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->ready
a 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.