Sto lottando con la Sezione 5.1.2.4 della norma C11, in particolare la semantica di Release / Acquire. Lo noto https://preshing.com/20120913/acquire-and-release-semantics/ (tra gli altri) afferma che:
... La semantica di rilascio impedisce il riordino della memoria del write-release con qualsiasi operazione di lettura o scrittura che la precede nell'ordine del programma.
Quindi, per quanto segue:
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 ;
}
dove vengono eseguiti:
> 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) ;
Mi aspetto quindi che il thread "1" abbia r1 == 1 e il thread "2" abbia r2 = 4.
Me lo aspetterei perché (in seguito ai paragrafi 16 e 18 della sezione 5.1.2.4):
- tutte le letture e le scritture (non atomiche) sono "sequenziate prima" e quindi "avvengono prima" della scrittura / versione atomica nel thread "1",
- quale "inter-thread-succede-prima" l'atomico legge / acquisisce nel thread "2" (quando legge "vero"),
- che a sua volta è "sequenziato prima" e quindi "accade prima" il (non atomico) legge e scrive (nel thread "2").
Tuttavia, è del tutto possibile che non ho capito lo standard.
Osservo che il codice generato per x86_64 include:
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
E a condizione che R1 e X1 avvengano in quell'ordine, questo dà il risultato che mi aspetto.
Ma la mia comprensione di x86_64 è che le letture avvengono in ordine con altre letture e scritture avvengono in ordine con altre scritture, ma le letture e le scritture potrebbero non avvenire in ordine tra loro. Ciò implica che è possibile che X1 accada prima di R1, e anche che X1, X2, W2, R1 avvengano in quell'ordine - credo. [Questo sembra disperatamente improbabile, ma se R1 fosse trattenuto da alcuni problemi di cache?]
Per favore: cosa non capisco?
Noto che se cambio i carichi / negozi di ts->readya memory_order_seq_cst, il codice generato per i negozi è:
xchg %cl,(%rdi)
che è coerente con la mia comprensione di x86_64 e darà il risultato che mi aspetto.
8.2.3.3 Stores Are Not Reordered With Earlier Loads. Quindi il tuo compilatore sta traducendo correttamente il tuo codice (che sorpresa), in modo tale che il tuo codice sia effettivamente completamente sequenziale e non accada nulla di interessante contemporaneamente.