Saya berjuang dengan Bagian 5.1.2.4 dari Standar C11, khususnya semantik Release / Acquire. Saya perhatikan bahwa https://preshing.com/20120913/acquire-and-release-semantics/ (antara lain) menyatakan bahwa:
... Rilis semantik mencegah penyusunan ulang memori dari rilis-rilis dengan operasi baca atau tulis apa pun yang mendahuluinya dalam urutan program.
Jadi, untuk yang berikut ini:
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 ;
}
di mana mereka dieksekusi:
> 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) ;
Karena itu, saya berharap utas "1" memiliki r1 == 1 dan utas "2" memiliki r2 = 4.
Saya mengharapkan itu karena (mengikuti paragraf 16 dan 18 dari sekte 5.1.2.4):
- semua (bukan atom) membaca dan menulis adalah "diurutkan sebelum" dan karenanya "terjadi sebelum" atom menulis / melepaskan di utas "1",
- yang "inter-thread-terjadi-sebelum" atom membaca / memperoleh di utas "2" (ketika dibaca 'benar'),
- yang pada gilirannya "diurutkan sebelum" dan karenanya "terjadi sebelum" (bukan atom) membaca dan menulis (di utas "2").
Namun, sangat mungkin bahwa saya gagal memahami standar.
Saya amati bahwa kode yang dihasilkan untuk x86_64 termasuk:
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
Dan asalkan R1 dan X1 terjadi dalam urutan itu, ini memberikan hasil yang saya harapkan.
Tetapi pemahaman saya tentang x86_64 adalah bahwa pembacaan terjadi secara berurutan dengan pembacaan lain dan penulisan terjadi sesuai dengan penulisan lainnya, tetapi pembacaan dan penulisan mungkin tidak terjadi secara berurutan. Yang menyiratkan adalah mungkin untuk X1 terjadi sebelum R1, dan bahkan untuk X1, X2, W2, R1 terjadi dalam urutan itu - saya percaya. [Ini sepertinya sangat tidak mungkin, tetapi jika R1 ditahan oleh beberapa masalah cache?]
Tolong: apa yang tidak saya mengerti?
Saya perhatikan bahwa jika saya mengubah beban / toko ts->ready
ke memory_order_seq_cst
, kode yang dihasilkan untuk toko adalah:
xchg %cl,(%rdi)
yang konsisten dengan pemahaman saya tentang x86_64 dan akan memberikan hasil yang saya harapkan.
8.2.3.3 Stores Are Not Reordered With Earlier Loads
. Jadi kompiler Anda menerjemahkan kode Anda dengan benar (betapa mengejutkannya), sehingga kode Anda secara efektif benar-benar berurutan dan tidak ada yang menarik terjadi secara bersamaan.