Const có nghĩa là an toàn luồng trong C ++ 11 không?


115

Tôi nghe nói rằng điều đó constcó nghĩa là an toàn luồng trong C ++ 11 . Có đúng như vậy không?

Điều đó có nghĩa là consthiện nay tương đương với Java s' synchronized?

Họ đang hết từ khóa ?


1
C ++ - faq thường được quản lý bởi cộng đồng C ++ và bạn có thể vui lòng đến và hỏi chúng tôi ý kiến ​​trong cuộc trò chuyện của chúng tôi.
Puppy

@DeadMG: Tôi không biết về C ++ - faq và nghi thức của nó, điều này đã được gợi ý trong một bình luận.
K-ball

2
Bạn đã nghe nói rằng const có nghĩa là an toàn luồng ở đâu?
Mark B

2
@Mark B: Herb SutterBjarne Stroustrup đã nói như vậy tại Standard C ++ Foundation , hãy xem liên kết ở cuối câu trả lời.
K-ball

LƯU Ý CHO NHỮNG ĐIỀU ĐẾN ĐÂY: câu hỏi thực sự KHÔNG phải là liệu const có nghĩa là an toàn cho luồng hay không. Điều đó sẽ là vô nghĩa, vì nếu không thì có nghĩa là bạn có thể tiếp tục và đánh dấu mọi phương pháp an toàn luồng là const. Thay vào đó, câu hỏi chúng tôi thực sự đang hỏi là có an toàn chuỗi const IMPLIES không và đó là nội dung cuộc thảo luận này nói về.
user541686,

Câu trả lời:


131

Tôi nghe nói rằng điều đó constcó nghĩa là an toàn luồng trong C ++ 11 . Có đúng như vậy không?

Nó có phần đúng ...

Đây là những gì Ngôn ngữ chuẩn phải nói về an toàn luồng:

[1.10 / 4] Hai đánh giá biểu thức xung đột nếu một trong số chúng sửa đổi vị trí bộ nhớ (1.7) và đánh giá kia truy cập hoặc sửa đổi cùng vị trí bộ nhớ.

[1.10 / 21] Việc thực thi một chương trình chứa một cuộc đua dữ liệu nếu nó chứa hai hành động xung đột trong các luồng khác nhau, ít nhất một trong số đó không phải là nguyên tử và không xảy ra trước hành động kia. Bất kỳ cuộc đua dữ liệu nào như vậy đều dẫn đến hành vi không xác định.

không có gì khác ngoài điều kiện đủ để một cuộc chạy đua dữ liệu xảy ra:

  1. Có hai hoặc nhiều hành động được thực hiện cùng một lúc trên một vật nhất định; và
  2. Ít nhất một trong số chúng là một bài viết.

Các thư viện chuẩn được xây dựng trên đó, đi một chút nữa:

[17.6.5.9/1] Phần này quy định các yêu cầu mà việc triển khai phải đáp ứng để ngăn chặn các cuộc chạy đua dữ liệu (1.10). Mọi chức năng thư viện tiêu chuẩn phải đáp ứng từng yêu cầu trừ khi có quy định khác. Việc triển khai có thể ngăn chặn việc chạy đua dữ liệu trong các trường hợp khác với những trường hợp được chỉ định bên dưới.

[17.6.5.9/3] Một hàm thư viện chuẩn C ++ không được sửa đổi trực tiếp hoặc gián tiếp các đối tượng (1.10) có thể truy cập bởi các luồng khác với luồng hiện tại trừ khi các đối tượng được truy cập trực tiếp hoặc gián tiếp thông qua cácđối số non-const của hàm, bao gồmthis.

nói cách đơn giản rằng nó mong muốn các hoạt động trên constcác đối tượng được an toàn theo luồng . Điều này có nghĩa là Thư viện chuẩn sẽ không giới thiệu một cuộc đua dữ liệu miễn là các hoạt động trên constcác đối tượng thuộc loại của riêng bạn

  1. Bao gồm hoàn toàn các lần đọc - nghĩa là không có bài viết nào--; hoặc là
  2. Đồng bộ hóa nội bộ ghi.

Nếu kỳ vọng này không phù hợp với một trong các loại của bạn, thì việc sử dụng nó trực tiếp hoặc gián tiếp cùng với bất kỳ thành phần nào của Thư viện Chuẩn có thể dẫn đến một cuộc đua dữ liệu . Tóm lại, constđiều đó có nghĩa là an toàn chuỗi theo quan điểm Thư viện Chuẩn . Điều quan trọng cần lưu ý là đây chỉ đơn thuần là một hợp đồng và nó sẽ không được trình biên dịch thực thi, nếu bạn phá vỡ nó, bạn sẽ có hành vi không xác định và bạn đang tự xử lý. Việc constcó xuất hiện hay không sẽ không ảnh hưởng đến việc tạo mã - ít nhất là không ảnh hưởng đến các cuộc đua dữ liệu -.

Điều đó có nghĩa là consthiện nay tương đương với Java s' synchronized?

Không . Không có gì...

Hãy xem xét lớp đơn giản quá mức sau đây đại diện cho một hình chữ nhật:

class rect {
    int width = 0, height = 0;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        width = new_width;
        height = new_height;
    }
    int area() const {
        return width * height;
    }
};

Thành viên-chức năng arealuồng an toàn ; không phải vì nó const, mà bởi vì nó bao gồm hoàn toàn các thao tác đọc. Không có lần ghi nào liên quan, và ít nhất một lần ghi liên quan là cần thiết để một cuộc đua dữ liệu xảy ra. Điều đó có nghĩa là bạn có thể gọi areatừ bao nhiêu chủ đề tùy thích và bạn sẽ nhận được kết quả chính xác mọi lúc.

Lưu ý rằng điều này không có nghĩa là rectthread-safe . Trên thực tế, thật dễ dàng để biết nếu một cuộc gọi đến areaxảy ra cùng lúc với một cuộc gọi đến set_sizemột số đã cho rect, thì areacuối cùng có thể tính toán kết quả của nó dựa trên chiều rộng cũ và chiều cao mới (hoặc thậm chí trên các giá trị bị cắt xén) .

Nhưng điều đó không sao cả, rectkhông phải constvì vậy nó thậm chí không được mong đợi là an toàn cho chuỗi . const rectMặt khác, một đối tượng được khai báo sẽ an toàn theo luồng vì không thể ghi (và nếu bạn đang xem xét const_cast-ing một cái gì đó được khai báo ban đầu constthì bạn sẽ nhận được hành vi không xác định và đó là nó).

Vậy nó có nghĩa là gì?

Hãy giả sử - vì lợi ích của lập luận - rằng các phép toán nhân cực kỳ tốn kém và chúng ta nên tránh chúng khi có thể. Chúng tôi chỉ có thể tính toán khu vực nếu nó được yêu cầu và sau đó lưu vào bộ nhớ cache trong trường hợp nó được yêu cầu lại trong tương lai:

class rect {
    int width = 0, height = 0;

    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        cached_area_valid = ( width == new_width && height == new_height );
        width = new_width;
        height = new_height;
    }
    int area() const {
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

[Nếu ví dụ này có vẻ quá giả tạo, bạn có thể thay thế intbằng một số nguyên được phân bổ động rất lớn vốn dĩ không an toàn cho chuỗi và các phép nhân cực kỳ tốn kém.]

Hàm thành viên area không còn an toàn theo luồng nữa , nó đang thực hiện ghi và không được đồng bộ hóa nội bộ. Đó có phải là vấn đề? Các cuộc gọi đến areacó thể xảy ra như là một phần của một bản sao-constructor của đối tượng khác, chẳng hạn nhà xây dựng có thể được gọi bằng một số hoạt động trên một container tiêu chuẩn , và tại thời điểm đó các thư viện chuẩn dự kiến hoạt động này để hành xử như một đọc liên quan đến chủng tộc dữ liệu . Nhưng chúng tôi đang viết!

Ngay sau khi chúng tôi đặt một rectvùng chứa tiêu chuẩn - trực tiếp hoặc gián tiếp - chúng tôi đang ký hợp đồng với Thư viện tiêu chuẩn . Để tiếp tục ghi trong một constchức năng trong khi vẫn tôn trọng hợp đồng đó, chúng tôi cần đồng bộ hóa nội bộ các ghi:

class rect {
    int width = 0, height = 0;

    mutable std::mutex cache_mutex;
    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        if( new_width != width || new_height != height )
        {
            std::lock_guard< std::mutex > guard( cache_mutex );
        
            cached_area_valid = false;
        }
        width = new_width;
        height = new_height;
    }
    int area() const {
        std::lock_guard< std::mutex > guard( cache_mutex );
        
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

Lưu ý rằng chúng tôi đã làm cho areahàm an toàn theo chuỗi , nhưng hàm rectvẫn không an toàn cho chuỗi . Một cuộc gọi areaxảy ra cùng lúc mà một cuộc gọi đến set_sizevẫn có thể tính toán sai giá trị, vì các nhiệm vụ cho widthheightkhông được bảo vệ bởi mutex.

Nếu chúng tôi thực sự muốn một luồng an toàn rect , chúng tôi sẽ sử dụng một nguyên thủy đồng bộ hóa để bảo vệ không an toàn cho luồng rect .

Họ đang hết từ khóa ?

Đúng vậy. Họ đã hết từ khóa kể từ ngày đầu tiên.


Nguồn : Bạn chưa biết constmutable - Herb Sutter


6
@Ben Voigt: Tôi hiểu rằng đặc tả C ++ 11 cho std::stringđược truyền đạt theo cách đã cấm COW . Tôi không nhớ chi tiết cụ thể, mặc dù ...
K-ball

3
@BenVoigt: Không. Nó chỉ ngăn những thứ như vậy không được đồng bộ hóa - tức là không an toàn cho luồng. C ++ 11 đã cấm COW một cách rõ ràng - đoạn văn cụ thể này không liên quan gì đến điều đó, và sẽ không cấm COW.
Puppy

2
Đối với tôi, dường như có một khoảng cách logic. [17.6.5.9/3] cấm "quá nhiều" bằng cách nói "nó sẽ không trực tiếp hoặc gián tiếp sửa đổi"; nó nên nói "sẽ không trực tiếp hoặc gián tiếp giới thiệu một cuộc đua dữ liệu", trừ khi một bài viết nguyên tử ở đâu đó được xác định không phải là "sửa đổi". Nhưng tôi không thể tìm thấy điều này ở bất cứ đâu.
Andy Prowl

1
Tôi có lẽ đã nói rõ hơn toàn bộ ý kiến ​​của mình ở đây: isocpp.org/blog/2012/12/… Dù sao cũng cảm ơn bạn đã cố gắng giúp đỡ.
Andy Prowl

1
đôi khi tôi tự hỏi ai là người (hoặc những người trực tiếp liên quan) thực sự chịu trách nhiệm viết ra một số đoạn văn chuẩn như thế này.
Pepper_chico
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.