Chúng ta hãy struct này:C++ 11 nguyên tử: tại sao mã này hoạt động?
struct entry {
atomic<bool> valid;
atomic_flag writing;
char payload[128];
}
Hai treads A và B đồng thời truy cập struct này theo cách này (giả sử e
là một thể hiện của entry
):
if (e.valid) {
// do something with e.payload...
} else {
while (e.writing.test_and_set(std::memory_order_acquire));
if (!e.valid) {
// write e.payload one byte at a time
// (the payload written by A may be different from the payload written by B)
e.valid = true;
e.writing.clear(std::memory_order_release);
}
}
Tôi đoán rằng mã này là chính xác và không trình bày vấn đề, nhưng tôi muốn hiểu tại sao nó hoạt động.
Trích dẫn tiêu chuẩn C++ (29.3.13):
Triển khai nên các cửa hàng nguyên tử có thể nhìn thấy tải nguyên tử trong một số tiền hợp lý thời gian.
Bây giờ, hãy ghi nhớ rằng cả hai luồng A và B đều nhập khối else
. Điều này xen kẽ có thể?
- Cả
A
vàB
nhập chi nhánhelse
, vìvalid
làfalse
A
đặtwriting
cờB
bắt đầu quay khóa trên lá cờwriting
A
đọcvalid
cờ (đó làfalse
) và nhập vàoif
khốiA
ghi trọng tảiA
viếttrue
trên cờ hợp lệ; Rõ ràng, nếuA
đọcvalid
một lần nữa, nó sẽ đọctrue
A
xóa cờwriting
B
đặt cờwriting
B
đọc một giá trị cũ của lá cờ hợp lệ (false
) và đi vào khốiif
B
ghi trọng tải của nóB
viếttrue
trênvalid
cờB
xóawriting
cờ
Tôi hy vọng điều này là không thể, nhưng khi nói đến thực sự trả lời câu hỏi "tại sao nó không phải là có thể?", Tôi không chắc chắn về câu trả lời. Đây là ý tưởng của tôi.
Trích dẫn từ tiêu chuẩn một lần nữa (29.3.12):
Atomic đọc-chỉnh sửa-ghi hoạt động sẽ luôn luôn đọc giá trị cuối cùng (theo thứ tự sửa đổi) bằng văn bản trước khi ghi liên quan đến đọc -modify-write operation.
atomic_flag::test_and_set()
là hoạt động đọc-sửa đổi nguyên tử, như đã nêu trong 29.7.5.
Kể từ atomic_flag::test_and_set()
luôn đọc một "giá trị mới", và tôi gọi nó với thứ tự bộ nhớ std::memory_order_acquire
, sau đó tôi không thể đọc một giá trị cũ của valid
cờ, bởi vì tôi phải xem tất cả các tác dụng phụ gây ra trước A
trước cuộc gọi atomic_flag::clear()
(sử dụng std::memory_order_release
).
Tôi có đúng không?
Làm rõ. Toàn bộ lý do của tôi (sai hoặc chính xác) dựa trên 29.3.12. Đối với những gì tôi đã hiểu cho đến nay, nếu chúng tôi bỏ qua số atomic_flag
, hãy đọc dữ liệu cũ từ valid
có thể ngay cả khi đó là atomic
. atomic
dường như không có nghĩa là "luôn hiển thị ngay lập tức" cho mọi chuỗi. Bảo đảm tối đa bạn có thể yêu cầu là một thứ tự nhất quán trong các giá trị bạn đọc, nhưng bạn vẫn có thể đọc dữ liệu cũ trước khi nhận được dữ liệu mới. May mắn thay, atomic_flag::test_and_set()
và mọi hoạt động exchange
đều có tính năng quan trọng này: chúng luôn đọc dữ liệu mới. Vì vậy, chỉ khi bạn có được/phát hành trên cờ writing
(không chỉ trên valid
), thì bạn sẽ có được hành vi mong đợi. Bạn có thấy quan điểm của tôi (đúng hay không)?
EDIT: câu hỏi ban đầu của tôi bao gồm một vài dòng sau đó đã đạt được quá nhiều sự chú ý nếu so với lõi của câu hỏi. Tôi để chúng phù hợp với các câu trả lời đã được đưa ra, nhưng hãy bỏ qua chúng nếu bạn đang đọc câu hỏi ngay bây giờ.
Có bất kỳ điểm nào trongvalid
là mộtatomic<bool>
vàkhông phải là một đồng bằngbool
? Hơn nữa, nếu nó phải là mộtatomic<bool>
,hạn chế thứ tự bộ nhớ 'tối thiểu' của nó là gì sẽ không hiển thịvấn đề?
Mô hình bộ nhớ C++ 11 nghĩ về "sắp xếp", không phải "hiển thị", bởi vì thứ tự là đủ miễn là khả năng hiển thị xảy ra cuối cùng. (Tôi nghĩ rằng bạn hiểu điều này sau khi bạn trò chuyện với @Grizzly, nhưng tôi cảm thấy như nhắc lại nó ở đây.) Mã này là tốt như bằng văn bản. – Nemo