Tôi đang vật lộn với Mục 5.1.2.4 của Tiêu chuẩn C11, đặc biệt là ngữ nghĩa của Phát hành / Mua lại. Tôi lưu ý rằng https://preshing.com/20120913/acquire-and-release-semantics/ (trong số những người khác) nói rằng:
... Ngữ nghĩa phát hành ngăn chặn sắp xếp lại bộ nhớ của bản phát hành ghi với bất kỳ thao tác đọc hoặc ghi nào trước nó theo thứ tự chương trình.
Vì vậy, cho những điều sau đây:
typedef struct test_struct
{
_Atomic(bool) ready ;
int v1 ;
int v2 ;
} test_struct_t ;
extern void
test_init(test_struct_t* ts, int v1, int v2)
{
ts->v1 = v1 ;
ts->v2 = v2 ;
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}
extern int
test_thread_1(test_struct_t* ts, int v2)
{
int v1 ;
while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v2 = v2 ; // expect read to happen before store/release
v1 = ts->v1 ; // expect write to happen before store/release
atomic_store_explicit(&ts->ready, true, memory_order_release) ;
return v1 ;
}
extern int
test_thread_2(test_struct_t* ts, int v1)
{
int v2 ;
while (!atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v1 = v1 ;
v2 = ts->v2 ; // expect write to happen after store/release in thread "1"
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
return v2 ;
}
nơi những cái đó được thực thi:
> in the "main" thread: test_struct_t ts ;
> test_init(&ts, 1, 2) ;
> start thread "2" which does: r2 = test_thread_2(&ts, 3) ;
> start thread "1" which does: r1 = test_thread_1(&ts, 4) ;
Do đó, tôi mong muốn chủ đề "1" có r1 == 1 và chủ đề "2" có r2 = 4.
Tôi mong đợi điều đó bởi vì (theo ký sinh 16 và 18 của giáo phái 5.1.2.4):
- tất cả các lần đọc và ghi (không phải nguyên tử) là "được giải trình tự trước" và do đó "xảy ra trước" việc ghi / giải phóng nguyên tử trong luồng "1",
- trong đó "inter-thread-xảy ra-trước" nguyên tử đọc / thu nhận trong luồng "2" (khi nó đọc 'true'),
- lần lượt là "tuần tự trước" và do đó "xảy ra trước" (không phải nguyên tử) đọc và ghi (trong luồng "2").
Tuy nhiên, hoàn toàn có thể là tôi đã không hiểu được tiêu chuẩn.
Tôi quan sát rằng mã được tạo cho x86_64 bao gồm:
test_thread_1:
movzbl (%rdi),%eax -- atomic_load_explicit(&ts->ready, memory_order_acquire)
test $0x1,%al
jne <test_thread_1> -- while is true
mov %esi,0x8(%rdi) -- (W1) ts->v2 = v2
mov 0x4(%rdi),%eax -- (R1) v1 = ts->v1
movb $0x1,(%rdi) -- (X1) atomic_store_explicit(&ts->ready, true, memory_order_release)
retq
test_thread_2:
movzbl (%rdi),%eax -- atomic_load_explicit(&ts->ready, memory_order_acquire)
test $0x1,%al
je <test_thread_2> -- while is false
mov %esi,0x4(%rdi) -- (W2) ts->v1 = v1
mov 0x8(%rdi),%eax -- (R2) v2 = ts->v2
movb $0x0,(%rdi) -- (X2) atomic_store_explicit(&ts->ready, false, memory_order_release)
retq
Và với điều kiện là R1 và X1 xảy ra theo thứ tự đó, điều này mang lại kết quả mà tôi mong đợi.
Nhưng sự hiểu biết của tôi về x86_64 là việc đọc xảy ra theo thứ tự với các lần đọc và ghi khác xảy ra theo thứ tự với các lần ghi khác, nhưng đọc và viết có thể không xảy ra theo thứ tự với nhau. Điều đó ngụ ý rằng X1 có thể xảy ra trước R1 và ngay cả đối với X1, X2, W2, R1 cũng xảy ra theo thứ tự đó - tôi tin. [Điều này dường như rất khó xảy ra, nhưng nếu R1 bị xử lý bởi một số vấn đề về bộ đệm?]
Xin vui lòng: những gì tôi không hiểu?
Tôi lưu ý rằng nếu tôi thay đổi tải / cửa hàng ts->ready
thành memory_order_seq_cst
, mã được tạo cho các cửa hàng là:
xchg %cl,(%rdi)
phù hợp với hiểu biết của tôi về x86_64 và sẽ cho kết quả mà tôi mong đợi.
8.2.3.3 Stores Are Not Reordered With Earlier Loads
. Vì vậy, trình biên dịch của bạn đang dịch chính xác mã của bạn (thật đáng ngạc nhiên), sao cho mã của bạn hoàn toàn tuần tự và không có gì thú vị xảy ra đồng thời.