Я бореться з розділом 5.1.2.4 стандарту C11, зокрема з семантикою випуску / придбання. Зауважу, що 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.
Я б очікував цього, оскільки (відповідно до пунктів 16 та 18 секти 5.1.2.4):
- усі (не атомні) зчитування та записи "секвенуються раніше" і, отже, "відбуваються перед" атомним записом / випуском у потоці "1",
- який "inter-thread-буває-перед" атомним читанням / набуттям в потоці "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
. Тож ваш компілятор правильно переводить ваш код (як це дивно), таким чином, що ваш код фактично повністю послідовний, і нічого цікавого не відбувається одночасно.