Liệu sự bất biến hoàn toàn loại bỏ sự cần thiết của khóa trong lập trình đa bộ xử lý?


39

Phần 1

Rõ ràng tính không thay đổi giảm thiểu nhu cầu về khóa trong lập trình đa bộ xử lý, nhưng liệu nó có loại bỏ được nhu cầu đó không, hoặc có những trường hợp mà tính bất biến không đủ? Dường như với tôi rằng bạn chỉ có thể trì hoãn việc xử lý và đóng gói trạng thái rất lâu trước khi hầu hết các chương trình phải thực sự làm gì đó (cập nhật kho lưu trữ dữ liệu, tạo báo cáo, ném ngoại lệ, v.v.). Những hành động như vậy luôn luôn có thể được thực hiện mà không có khóa? Liệu hành động đơn thuần là ném ra từng vật thể và tạo ra một vật thể mới thay vì thay đổi nguyên bản (một cái nhìn thô thiển về tính bất biến) sẽ bảo vệ tuyệt đối khỏi sự tranh chấp giữa các quá trình, hay có những trường hợp góc nào vẫn cần khóa?

Tôi biết rất nhiều lập trình viên và nhà toán học chức năng thích nói về "không có tác dụng phụ" nhưng trong "thế giới thực", mọi thứ đều có tác dụng phụ, ngay cả khi đó là thời gian cần thiết để thực hiện một lệnh máy. Tôi quan tâm đến cả câu trả lời lý thuyết / học thuật và câu trả lời thực tế / thực tế.

Nếu tính bất biến là an toàn, với các giới hạn hoặc giả định nhất định, tôi muốn biết chính xác đường biên của "vùng an toàn" là gì. Một số ví dụ về ranh giới có thể:

  • Tôi / O
  • Ngoại lệ / lỗi
  • Tương tác với các chương trình được viết bằng các ngôn ngữ khác
  • Tương tác với các máy khác (vật lý, ảo hoặc lý thuyết)

Đặc biệt cảm ơn @JimmaHoffa vì bình luận của anh ấy đã bắt đầu câu hỏi này!

Phần 2

Lập trình đa bộ xử lý thường được sử dụng như một kỹ thuật tối ưu hóa - để làm cho một số mã chạy nhanh hơn. Khi nào thì nhanh hơn để sử dụng khóa so với các đối tượng bất biến?

Đưa ra các giới hạn được quy định trong Luật của Amdahl , khi nào bạn có thể đạt được hiệu suất cao hơn tất cả (có hoặc không có người thu gom rác được tính đến) với các đối tượng không thay đổi so với khóa các đối tượng có thể thay đổi?

Tóm lược

Tôi đang kết hợp hai câu hỏi này thành một để cố gắng tìm ra vị trí của hộp giới hạn cho tính không thay đổi như một giải pháp cho các vấn đề luồng.


21
but everything has a side effect- Uh, không nó không. Hàm chấp nhận một số giá trị và trả về một số giá trị khác và không làm phiền bất cứ thứ gì bên ngoài hàm, không có tác dụng phụ và do đó an toàn cho luồng. Không quan trọng là máy tính sử dụng điện. Chúng ta cũng có thể nói về các tia vũ trụ đánh vào các tế bào bộ nhớ, nếu bạn muốn, nhưng hãy giữ cho cuộc tranh luận thực tế. Nếu bạn muốn xem xét những thứ như cách thức hoạt động của chức năng ảnh hưởng đến mức tiêu thụ điện năng, thì đó là một vấn đề khác với lập trình luồng an toàn.
Robert Harvey

5
@RobertHarvey - Có lẽ tôi chỉ đang sử dụng một định nghĩa khác về tác dụng phụ và tôi nên nói, "hiệu ứng phụ trong thế giới thực" thay vào đó. Vâng, các nhà toán học có chức năng mà không có tác dụng phụ. Mã thực thi trên máy trong thế giới thực sẽ lấy tài nguyên máy để thực thi, cho dù nó có làm thay đổi dữ liệu hay không. Hàm trong ví dụ của bạn đặt giá trị trả về của nó trên ngăn xếp trong hầu hết các kiến ​​trúc máy.
GlenPeterson

1
Nếu bạn thực sự có thể vượt qua nó, tôi nghĩ rằng câu hỏi của bạn đi vào trung tâm của giấy khét tiếng này research.microsoft.com/en-us/um/people/simonpj/papers/...
Jimmy Hoffa

6
Đối với mục đích thảo luận của chúng tôi, tôi giả sử rằng bạn đang đề cập đến một máy hoàn chỉnh Turing đang thực hiện một số loại ngôn ngữ lập trình được xác định rõ, trong đó các chi tiết triển khai không liên quan. Nói cách khác, không quan trọng ngăn xếp đang làm gì, nếu chức năng tôi viết bằng ngôn ngữ lập trình mà tôi chọn có thể đảm bảo tính bất biến trong giới hạn của ngôn ngữ. Tôi không nghĩ về ngăn xếp khi tôi lập trình bằng ngôn ngữ cấp cao, tôi cũng không cần phải làm vậy.
Robert Harvey

1
@RobertHarvey thìa; Monads heh Và bạn có thể thu thập điều đó từ vài trang đầu tiên. Tôi đề cập đến nó bởi vì trong toàn bộ nó chi tiết một kỹ thuật xử lý các tác dụng phụ theo cách thực tế thuần túy, tôi khá chắc chắn rằng nó sẽ trả lời câu hỏi của Glen, vì vậy đã đăng nó như một ghi chú chân tốt cho bất kỳ ai tìm thấy câu hỏi này tương lai để đọc thêm.
Jimmy Hoffa

Câu trả lời:


35

Đây là một câu hỏi kỳ quặc thực sự, thực sự rộng nếu được trả lời đầy đủ. Tôi sẽ tập trung vào việc làm sáng tỏ một số chi tiết cụ thể mà bạn đang hỏi về.

Bất biến là một thiết kế đánh đổi. Nó làm cho một số thao tác khó hơn (sửa đổi trạng thái trong các đối tượng lớn một cách nhanh chóng, xây dựng các đối tượng từng phần, giữ trạng thái hoạt động, v.v.) có lợi cho người khác (gỡ lỗi dễ dàng hơn, suy luận dễ dàng hơn về hành vi của chương trình, không phải lo lắng về những điều thay đổi bên dưới bạn khi làm việc đồng thời, v.v.). Đây là câu hỏi cuối cùng mà chúng tôi quan tâm với câu hỏi này, nhưng tôi muốn nhấn mạnh rằng nó là một công cụ. Một công cụ tốt thường giải quyết được nhiều vấn đề hơn nó gây ra (trong hầu hết các chương trình hiện đại ), nhưng không phải là viên đạn bạc ... Không phải là thứ thay đổi hành vi nội tại của các chương trình.

Bây giờ, những gì nó có được bạn? Tính bất biến giúp bạn có một điều: bạn có thể đọc đối tượng bất biến một cách tự do, mà không phải lo lắng về trạng thái của nó thay đổi bên dưới bạn (giả sử nó thực sự vô cùng sâu sắc ... Có một đối tượng bất biến với các thành viên có thể thay đổi thường là một kẻ phá vỡ thỏa thuận). Đó là nó. Nó giải phóng bạn khỏi việc phải quản lý đồng thời (thông qua các khóa, ảnh chụp nhanh, phân vùng dữ liệu hoặc các cơ chế khác; trọng tâm của câu hỏi ban đầu về các khóa là ... Không chính xác trong phạm vi câu hỏi).

Hóa ra mặc dù có rất nhiều thứ đọc đối tượng. IO có, nhưng bản thân IO có xu hướng không xử lý tốt việc sử dụng đồng thời. Hầu như tất cả các xử lý đều có, nhưng các đối tượng khác có thể có thể thay đổi hoặc chính quá trình xử lý có thể sử dụng trạng thái không thân thiện với đồng thời. Sao chép một đối tượng là một điểm rắc rối lớn ẩn trong một số ngôn ngữ vì một bản sao đầy đủ là (gần như) không bao giờ là một hoạt động nguyên tử. Đây là nơi các đối tượng bất biến giúp bạn.

Về hiệu suất, nó phụ thuộc vào ứng dụng của bạn. Khóa là (thường) nặng. Các cơ chế quản lý đồng thời khác nhanh hơn nhưng có tác động cao đến thiết kế của bạn. Nói chung , một thiết kế đồng thời cao sử dụng các vật thể bất biến (và tránh các điểm yếu của chúng) sẽ hoạt động tốt hơn một thiết kế đồng thời cao, khóa các đối tượng có thể thay đổi. Nếu chương trình của bạn đồng thời nhẹ thì nó phụ thuộc và / hoặc không quan trọng.

Nhưng hiệu suất không phải là mối quan tâm cao nhất của bạn. Viết chương trình đồng thời là khó . Gỡ lỗi chương trình đồng thời là khó . Các đối tượng không thay đổi giúp cải thiện chất lượng chương trình của bạn bằng cách loại bỏ các cơ hội để thực hiện lỗi quản lý đồng thời theo cách thủ công. Chúng giúp gỡ lỗi dễ dàng hơn vì bạn không cố gắng theo dõi trạng thái trong một chương trình đồng thời. Họ làm cho thiết kế của bạn đơn giản hơn và do đó loại bỏ các lỗi ở đó.

Vì vậy, để tóm tắt: tính bất biến giúp nhưng sẽ không loại bỏ các thách thức cần thiết để xử lý đồng thời đúng cách. Sự giúp đỡ đó có xu hướng lan tỏa, nhưng lợi ích lớn nhất là từ góc độ chất lượng hơn là hiệu suất. Và không, sự bất biến không thể tha thứ cho bạn khỏi việc quản lý đồng thời trong ứng dụng của bạn, xin lỗi.


+1 Điều này có ý nghĩa, nhưng bạn có thể đưa ra một ví dụ về nơi mà trong một ngôn ngữ bất biến sâu sắc mà bạn vẫn phải lo lắng về việc xử lý đồng thời đúng cách? Bạn nói rằng bạn làm, nhưng một kịch bản như vậy không rõ ràng đối với tôi
Jimmy Hoffa

@JimmyHoffa Trong một ngôn ngữ bất biến, bạn vẫn cần một cách nào đó để cập nhật trạng thái giữa các luồng. Hai ngôn ngữ bất biến nhất mà tôi biết (Clojure và Haskell) cung cấp một loại tham chiếu (nguyên tử và Mvars) cung cấp một cách để chuyển trạng thái được sửa đổi giữa các luồng. Các ngữ nghĩa của các loại ref của họ ngăn ngừa một số loại lỗi đồng thời nhất định, nhưng các loại khác vẫn có thể.
ném đá

@stonemetal thú vị, trong 4 tháng với Haskell tôi thậm chí chưa từng nghe về Mvars, tôi luôn nghe nói sử dụng STM để liên lạc trạng thái đồng thời hoạt động giống như thông điệp của Erlang mà tôi nghĩ. Mặc dù ví dụ hoàn hảo về tính không thay đổi không giải quyết được các vấn đề đồng thời tôi có thể nghĩ đến là đang cập nhật UI, nếu bạn có 2 luồng cố gắng cập nhật UI với các phiên bản dữ liệu khác nhau, một có thể mới hơn và do đó cần phải có bản cập nhật thứ hai để bạn có bản cập nhật thứ hai. điều kiện cuộc đua trong đó bạn phải đảm bảo trình tự bằng cách nào đó .. Suy nghĩ thú vị .. Cảm ơn đã biết chi tiết
Jimmy Hoffa

1
@jimmyhoffa - Ví dụ phổ biến nhất là IO. Ngay cả khi ngôn ngữ là bất biến, cơ sở dữ liệu / trang web / tệp của bạn cũng không. Một cái khác là bản đồ / giảm điển hình của bạn. Tính không thay đổi có nghĩa là sự tổng hợp của bản đồ dễ đánh lừa hơn, nhưng bạn vẫn cần xử lý 'một khi tất cả các bản đồ được thực hiện song song, giảm sự phối hợp'.
Telastyn

1
@JimmyHoffa: MVars là một nguyên thủy đồng thời có thể thay đổi ở mức độ thấp (về mặt kỹ thuật, một tham chiếu bất biến đến một vị trí lưu trữ có thể thay đổi), không quá khác biệt so với những gì bạn thấy trong các ngôn ngữ khác; bế tắc và điều kiện chủng tộc là rất có thể. STM là một bản tóm tắt đồng thời ở mức độ cao cho bộ nhớ chia sẻ có thể thay đổi không khóa (rất khác với truyền tin nhắn) cho phép các giao dịch có thể kết hợp mà không có khả năng bị bế tắc hoặc điều kiện cuộc đua. Dữ liệu bất biến chỉ là chủ đề an toàn, không có gì khác để nói về nó.
CA McCann

13

Hàm chấp nhận một số giá trị và trả về một số giá trị khác và không làm phiền bất cứ thứ gì bên ngoài hàm, không có tác dụng phụ và do đó an toàn cho luồng. Nếu bạn muốn xem xét những thứ như cách thức hoạt động của chức năng ảnh hưởng đến mức tiêu thụ điện năng, thì đó là một vấn đề khác.

Tôi giả sử rằng bạn đang đề cập đến một máy hoàn chỉnh Turing đang thực thi một số loại ngôn ngữ lập trình được xác định rõ, trong đó các chi tiết triển khai không liên quan. Nói cách khác, không quan trọng ngăn xếp đang làm gì, nếu chức năng tôi viết bằng ngôn ngữ lập trình mà tôi chọn có thể đảm bảo tính bất biến trong giới hạn của ngôn ngữ. Tôi không nghĩ về ngăn xếp khi tôi lập trình bằng ngôn ngữ cấp cao, tôi cũng không cần phải làm vậy.

Để minh họa cách thức hoạt động của nó, tôi sẽ cung cấp một vài ví dụ đơn giản trong C #. Để những ví dụ này là đúng, chúng ta phải đưa ra một vài giả định. Đầu tiên, trình biên dịch tuân theo đặc tả C # không có lỗi và thứ hai là nó tạo ra các chương trình chính xác.

Giả sử tôi muốn một hàm đơn giản chấp nhận bộ sưu tập chuỗi và trả về một chuỗi là nối của tất cả các chuỗi trong bộ sưu tập được phân tách bằng dấu phẩy. Một triển khai đơn giản, ngây thơ trong C # có thể trông như thế này:

public string ConcatenateWithCommas(ImmutableList<string> list)
{
    string result = string.Empty;
    bool isFirst = false;

    foreach (string s in list)
    {
        if (isFirst)
            result += s;
        else
            result += ", " + s;
    }
    return result;
} 

Ví dụ này là bất biến, prima facie. Làm thế nào để tôi biết điều đó? Vì stringđối tượng là bất biến. Tuy nhiên, việc thực hiện không lý tưởng. Bởi vì resultkhông thay đổi, một đối tượng chuỗi mới phải được tạo ra mỗi lần thông qua vòng lặp, thay thế đối tượng ban đầu resulttrỏ đến. Điều này có thể ảnh hưởng tiêu cực đến tốc độ và gây áp lực lên bộ thu gom rác, vì nó phải dọn sạch tất cả các chuỗi bổ sung đó.

Bây giờ, hãy nói tôi làm điều này:

public string ConcatenateWithCommas(ImmutableList<string> list)
{
    var result = new StringBuilder();
    bool isFirst = false;

    foreach (string s in list)
    {
        if (isFirst)
            result.Append(s);
        else
            result.Append(", " + s);
    }
    return result.ToString();
} 

Lưu ý rằng tôi đã thay thế string resultbằng một đối tượng có thể thay đổi , StringBuilder. Đây là nhiều nhanh hơn so với ví dụ đầu tiên, bởi vì một chuỗi mới không được tạo ra mỗi lần thông qua các vòng lặp. Thay vào đó, đối tượng StringBuilder chỉ thêm các ký tự từ mỗi chuỗi vào một tập hợp các ký tự và xuất ra toàn bộ ở cuối.

Hàm này có bất biến không, mặc dù StringBuilder có thể thay đổi?

Vâng, nó là. Tại sao? Bởi vì mỗi lần hàm này được gọi, một StringBuilder mới được tạo, chỉ dành cho cuộc gọi đó. Vì vậy, bây giờ chúng ta có một hàm thuần túy là an toàn luồng, nhưng chứa các thành phần có thể thay đổi.

Nhưng nếu tôi làm điều này thì sao?

public class Concatenate
{
    private StringBuilder result = new StringBuilder();
    bool isFirst = false;

    public string ConcatenateWithCommas(ImmutableList<string> list)
    {
        foreach (string s in list)
        {
            if (isFirst)
                result.Append(s);
            else
                result.Append(", " + s);
        }
        return result.ToString();
    } 
}

Phương pháp này có an toàn không? Không, không phải vậy. Tại sao? Bởi vì lớp hiện đang giữ trạng thái mà phương thức của tôi phụ thuộc vào. Một điều kiện cuộc đua hiện có trong phương thức: một luồng có thể sửa đổi IsFirst, nhưng một luồng khác có thể thực hiện lần đầu tiên Append(), trong trường hợp đó bây giờ tôi có dấu phẩy ở đầu chuỗi không được phép ở đó.

Tại sao tôi có thể muốn làm điều đó như thế này? Chà, tôi có thể muốn các chủ đề tích lũy các chuỗi vào của tôi resultmà không liên quan đến thứ tự, hoặc theo thứ tự các chủ đề đến. Có lẽ đó là một logger, ai biết?

Dù sao, để khắc phục nó, tôi đặt một locktuyên bố xung quanh các bộ phận của phương thức.

public class Concatenate
{
    private StringBuilder result = new StringBuilder();
    bool isFirst = false;
    private static object locker = new object();

    public string AppendWithCommas(ImmutableList<string> list)
    {
        lock (locker)
        {
            foreach (string s in list)
            {
                if (isFirst)
                    result.Append(s);
                else
                    result.Append(", " + s);
            }
            return result.ToString();
        }
    } 
}

Bây giờ nó lại an toàn cho chủ đề.

Cách duy nhất mà các phương thức bất biến của tôi có thể không thể an toàn cho luồng là nếu phương thức này bằng cách nào đó rò rỉ một phần của việc thực hiện. Điều này có thể xảy ra không? Không phải nếu trình biên dịch là chính xác và chương trình là chính xác. Tôi sẽ bao giờ cần khóa trên các phương pháp như vậy? Không.

Để biết ví dụ về cách triển khai có thể bị rò rỉ trong kịch bản tương tranh, xem tại đây .


2
Trừ khi tôi nhầm, vì a Listlà có thể thay đổi, trong hàm đầu tiên bạn tuyên bố 'thuần', một luồng khác có thể xóa tất cả các thành phần khỏi danh sách hoặc thêm một bó nữa trong vòng lặp foreach. Không chắc chắn như thế nào mà có thể chơi với IEnumeratorcon người while(iter.MoveNext())ed, nhưng trừ khi IEnumeratorlà bất biến (nghi ngờ) thì đó sẽ đe dọa đến thrash vòng lặp foreach.
Jimmy Hoffa

Đúng, bạn phải giả định rằng bộ sưu tập không bao giờ được viết trong khi các chủ đề đang đọc từ nó. Đó sẽ là một giả định hợp lệ, nếu mỗi luồng gọi phương thức xây dựng danh sách riêng của nó.
Robert Harvey

Tôi không nghĩ bạn có thể gọi nó là 'thuần túy' khi nó có đối tượng có thể thay đổi trong đó mà nó đang sử dụng bằng cách tham chiếu. Nếu nó nhận được IEnumerable, bạn thể đưa ra yêu cầu đó vì bạn không thể thêm hoặc xóa các phần tử khỏi IEnumerable, nhưng sau đó nó có thể là một Array hoặc Danh sách được trao dưới dạng IEnumerable để hợp đồng IEnumerable không đảm bảo bất kỳ hình thức nào của sự tinh khiết. Kỹ thuật thực sự để làm cho chức năng đó thuần túy sẽ là bất biến với việc sao chép, C # không làm điều này vì vậy bạn sẽ phải sao chép Danh sách ngay khi hàm nhận được; nhưng cách duy nhất để làm điều đó là với một lời rao giảng về nó ...
Jimmy Hoffa

1
@JimmyHoffa: Chết tiệt, bạn khiến tôi bị ám ảnh bởi vấn đề gà và trứng này! Nếu bạn thấy một giải pháp ở bất cứ đâu, xin vui lòng cho tôi biết.
Robert Harvey

1
Chỉ cần bắt gặp câu trả lời này ngay bây giờ và đó là một trong những lời giải thích tốt nhất về chủ đề tôi đã gặp, các ví dụ rất ngắn gọn và thực sự làm cho nó dễ dàng để tìm hiểu. Cảm ơn!
Stephen Byrne

4

Tôi không chắc chắn nếu tôi hiểu câu hỏi của bạn.

IMHO câu trả lời là có. Nếu tất cả các đối tượng của bạn là bất biến, thì bạn không cần bất kỳ ổ khóa nào. Nhưng nếu bạn cần duy trì trạng thái (ví dụ: bạn triển khai cơ sở dữ liệu hoặc bạn cần tổng hợp kết quả từ nhiều luồng) thì bạn cần sử dụng khả năng biến đổi và do đó cũng khóa. Tính không thay đổi giúp loại bỏ sự cần thiết của khóa, nhưng thông thường bạn không thể đủ khả năng để có các ứng dụng hoàn toàn bất biến.

Trả lời phần 2 - khóa phải luôn chậm hơn không khóa.


3
Phần hai là hỏi "Sự đánh đổi hiệu suất giữa các khóa và các cấu trúc bất biến là gì?" Nó có thể xứng đáng với câu hỏi của riêng mình, nếu nó thậm chí có thể trả lời được.
Robert Harvey

4

Đóng gói một loạt các trạng thái liên quan trong một tham chiếu có thể thay đổi duy nhất đến một đối tượng không thay đổi có thể giúp cho nhiều loại sửa đổi trạng thái được thực hiện không khóa bằng cách sử dụng mẫu:

do
{
   oldState = someObject.State;
   newState = oldState.WithSomeChanges();
} while (Interlocked.CompareExchange(ref someObject.State, newState, oldState) != oldState;

Nếu cả hai luồng cố gắng cập nhật someObject.stateđồng thời, cả hai đối tượng sẽ đọc trạng thái cũ và xác định trạng thái mới sẽ là gì nếu không có thay đổi của nhau. Chuỗi đầu tiên để thực hiện so sánhExExchange sẽ lưu trữ trạng thái mà nó nghĩ là trạng thái tiếp theo. Chuỗi thứ hai sẽ thấy rằng trạng thái không còn phù hợp với những gì nó đã đọc trước đó và do đó sẽ tính lại trạng thái tiếp theo phù hợp của hệ thống với các thay đổi của luồng đầu tiên có hiệu lực.

Mẫu này có lợi thế là một luồng được trả tiền không thể chặn tiến trình của các luồng khác. Nó có lợi thế hơn nữa là ngay cả khi có sự tranh chấp nặng nề, một số chủ đề sẽ luôn đạt được tiến bộ. Tuy nhiên, có một nhược điểm là trong sự hiện diện của sự tranh chấp, rất nhiều chủ đề có thể dành nhiều thời gian để thực hiện công việc mà cuối cùng họ sẽ loại bỏ. Ví dụ: nếu tất cả 30 luồng trên các CPU riêng biệt đều cố gắng thay đổi một đối tượng đồng thời, thì một luồng sẽ thành công ở lần thử đầu tiên, một lần thứ hai, một lần thứ ba, v.v ... để mỗi luồng kết thúc trung bình khoảng 15 lần thử để cập nhật dữ liệu của nó. Sử dụng khóa "tư vấn" có thể cải thiện đáng kể mọi thứ: trước khi một luồng thử cập nhật, nó sẽ kiểm tra xem chỉ báo "tranh chấp" có được đặt hay không. Nếu vậy, cần có một khóa trước khi thực hiện cập nhật. Nếu một luồng thực hiện một vài lần thử không thành công tại một bản cập nhật, thì nó sẽ đặt cờ tranh chấp. Nếu một chủ đề cố lấy khóa tìm thấy thì không có ai khác chờ đợi, nó sẽ xóa cờ tranh chấp. Lưu ý rằng khóa ở đây là không cần thiết cho "tính chính xác"; mã sẽ hoạt động chính xác ngay cả khi không có nó. Mục đích của khóa là để giảm thiểu lượng thời gian mã dành cho các hoạt động không có khả năng thành công.


4

Bạn bắt đầu với

Rõ ràng tính không thay đổi giảm thiểu sự cần thiết của khóa trong lập trình đa bộ xử lý

Sai rồi. Bạn cần đọc kỹ tài liệu cho mọi lớp học mà bạn sử dụng. Ví dụ: const std :: string trong C ++ không phải là luồng an toàn. Các đối tượng bất biến có thể có trạng thái bên trong thay đổi khi truy cập chúng.

Nhưng bạn đang xem xét điều này từ một quan điểm hoàn toàn sai lầm. Không quan trọng là một đối tượng có bất biến hay không, điều quan trọng là bạn có thay đổi nó hay không. Điều bạn đang nói giống như nói "nếu bạn không bao giờ thi bằng lái xe, bạn sẽ không bao giờ bị mất giấy phép lái xe khi lái xe khi say rượu". Đúng, nhưng thiếu điểm.

Bây giờ trong mã ví dụ ai đó đã viết với một hàm có tên là "ConcatenateWithCommas": Nếu đầu vào có thể thay đổi và bạn đã sử dụng khóa, bạn sẽ đạt được gì? Nếu người khác cố gắng sửa đổi danh sách trong khi bạn cố nối chuỗi, khóa có thể giúp bạn không bị hỏng. Nhưng bạn vẫn không biết liệu bạn nối các chuỗi trước hay sau khi các luồng khác thay đổi chúng. Vì vậy, kết quả của bạn là khá vô dụng. Bạn có một vấn đề không liên quan đến khóa và không thể khắc phục bằng khóa. Nhưng sau đó, nếu bạn sử dụng các đối tượng bất biến và luồng khác thay thế toàn bộ đối tượng bằng một đối tượng mới, bạn đang sử dụng đối tượng cũ chứ không phải đối tượng mới, vì vậy kết quả của bạn là vô ích. Bạn phải suy nghĩ về những vấn đề này ở cấp độ chức năng thực tế.


2
const std::stringlà một ví dụ nghèo nàn và một chút cá trích đỏ. Chuỗi C ++ có thể thay đổi và constdù sao cũng không thể đảm bảo tính bất biến. Tất cả những gì nó làm chỉ nói các constchức năng có thể được gọi. Tuy nhiên, các chức năng đó vẫn có thể thay đổi trạng thái bên trong và constcó thể bị loại bỏ. Cuối cùng, có cùng một vấn đề như bất kỳ ngôn ngữ nào khác: chỉ vì tài liệu tham khảo của tôiconst không có nghĩa là tài liệu tham khảo của bạn cũng vậy. Không, nên sử dụng cấu trúc dữ liệu thực sự bất biến.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.