Tôi nghe nói rằng điều đó const
có 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:
- 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à
- Í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 const
cá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 const
các đối tượng thuộc loại của riêng bạn
- 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à
- Đồ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 const
có 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à const
hiệ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 area
là luồ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 area
từ 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à rect
là thread-safe . Trên thực tế, thật dễ dàng để biết nếu một cuộc gọi đến area
xảy ra cùng lúc với một cuộc gọi đến set_size
một số đã cho rect
, thì area
cuố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ả, rect
không phải const
vì vậy nó thậm chí không được mong đợi là an toàn cho chuỗi . const rect
Mặ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 const
thì 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ế int
bằ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 area
có 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 rect
vù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 const
chứ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 area
hàm an toàn theo chuỗi , nhưng hàm rect
vẫn không an toàn cho chuỗi . Một cuộc gọi area
xảy ra cùng lúc mà một cuộc gọi đến set_size
vẫn có thể tính toán sai giá trị, vì các nhiệm vụ cho width
và height
khô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 const
vàmutable
- Herb Sutter