C11標準のセクション5.1.2.4、特にリリース/取得のセマンティクスに苦労しています。私は、https://preshing.com/20120913/acquire-and-release-semantics/(他のものの中でも)が次のように述べていることに注意します。
...リリースセマンティクスは、プログラムの順序でそれに先行する読み取りまたは書き込み操作による書き込みリリースのメモリの並べ替えを防ぎます。
したがって、次の場合:
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 ;
}
それらが実行される場所:
> 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) ;
したがって、スレッド "1"にはr1 == 1、スレッド "2"にはr2 = 4が必要です。
(セクション5.1.2.4の16項と18項に従って)次の理由により、
- すべての(アトミックではない)読み取りと書き込みは「前にシーケンス」され、したがってスレッド「1」でのアトミックな書き込み/解放の「前に発生」します。
- どの「inter-thread-happens-before」は、スレッド「2」でアトミックな読み取り/取得(「true」を読み取った場合)、
- これは順番に「前にシーケンス」され、したがって「アトミックではなく」前に発生します(スレッド「2」で)読み取りと書き込みを行います。
ただし、基準を理解できなかった可能性は十分にあります。
x86_64用に生成されたコードには次のものが含まれています。
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
そして、提供 R1とX1は、その順序で起こることを、これは私が期待する結果を与えます。
しかし、x86_64についての私の理解では、読み取りは他の読み取りと順番に行われ、他の書き込みと順番に行われますが、読み取りと書き込みは順番に行われない場合があります。つまり、X1がR1の前に発生する可能性があり、X1、X2、W2、R1がこの順序で発生する可能性もあります。[これは絶対にありそうにないように思われますが、R1がいくつかのキャッシュの問題によって停滞している場合は?]
どうぞ:私は何を理解していませんか?
のロード/ストアをに変更すると、ストアに対して生成されるコードは次のts->ready
ようmemory_order_seq_cst
になります。
xchg %cl,(%rdi)
これはx86_64についての私の理解と一致しており、期待した結果が得られます。
8.2.3.3 Stores Are Not Reordered With Earlier Loads
。そのため、コンパイラーはコードを正しく変換しています(驚くべきことです)。これにより、コードは実質的に完全にシーケンシャルになり、興味深いことは同時に発生しません。