Je me bats avec la section 5.1.2.4 de la norme C11, en particulier la sémantique de Release / Acquire. Je note que https://preshing.com/20120913/acquire-and-release-semantics/ (entre autres) déclare que:
... La sémantique de libération empêche la réorganisation de la mémoire de la libération en écriture avec toute opération de lecture ou d'écriture qui la précède dans l'ordre du programme.
Donc, pour ce qui suit:
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 ;
}
où ceux-ci sont exécutés:
> 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) ;
Je m'attends donc à ce que le thread "1" ait r1 == 1 et le thread "2" à r2 = 4.
Je m'attendrais à cela parce que (après les paragraphes 16 et 18 de la section 5.1.2.4):
- toutes les lectures et écritures (non atomiques) sont "séquencées avant" et donc "se produisent avant" l'écriture / libération atomique dans le thread "1",
- qui "inter-thread-se produit-avant" la lecture / acquisition atomique dans le thread "2" (quand il lit "vrai"),
- qui à son tour est "séquencé avant" et donc "se produit avant" le (non atomique) lit et écrit (dans le thread "2").
Cependant, il est tout à fait possible que je n'ai pas compris la norme.
J'observe que le code généré pour x86_64 comprend:
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
Et à condition que R1 et X1 se produisent dans cet ordre, cela donne le résultat que j'attends.
Mais ma compréhension de x86_64 est que les lectures se produisent dans l'ordre avec d'autres lectures et écritures se produisent dans l'ordre avec d'autres écritures, mais les lectures et les écritures peuvent ne pas se produire dans l'ordre les unes avec les autres. Ce qui implique qu'il est possible que X1 se produise avant R1, et même que X1, X2, W2, R1 se produisent dans cet ordre - je crois. [Cela semble désespérément improbable, mais si R1 était bloqué par certains problèmes de cache?]
S'il vous plaît: qu'est-ce que je ne comprends pas?
Je note que si je change les charges / magasins de ts->ready
en memory_order_seq_cst
, le code généré pour les magasins est:
xchg %cl,(%rdi)
ce qui est cohérent avec ma compréhension de x86_64 et donnera le résultat que j'attends.
8.2.3.3 Stores Are Not Reordered With Earlier Loads
. Ainsi, votre compilateur traduit correctement votre code (c'est surprenant), de sorte que votre code est effectivement complètement séquentiel et que rien d'intéressant ne se produit simultanément.