Walczę z sekcją 5.1.2.4 normy C11, w szczególności semantyką wydania / nabycia. Zauważam, że https://preshing.com/20120913/acquire-and-release-semantics/ (między innymi) stwierdza, że:
... Semantyka wydania zapobiega zmianie kolejności pamięci wydania-zapisu przy każdej operacji odczytu lub zapisu, która poprzedza ją w kolejności programów.
Tak więc dla następujących:
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 ;
}
gdzie są wykonywane:
> 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) ;
Oczekiwałbym zatem, że wątek „1” ma r1 == 1, a wątek „2” ma r2 = 4.
Spodziewałbym się tego, ponieważ (zgodnie z ust. 16 i 18 sekcji 5.1.2.4):
- wszystkie (nieatomowe) odczyty i zapisy są „sekwencjonowane przed”, a zatem „zdarzają się przed” zapisem / zwolnieniem atomowym w wątku „1”,
- który „między-wątkiem-dzieje-przed” atomowy odczyt / nabycie w wątku „2” (gdy czyta „prawda”),
- który z kolei jest „sekwencjonowany przed”, a zatem „dzieje się przed” (nie atomowy) odczytuje i zapisuje (w wątku „2”).
Jest jednak całkiem możliwe, że nie zrozumiałem normy.
Zauważam, że kod wygenerowany dla x86_64 obejmuje:
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
I pod warunkiem, że R1 i X1 występują w tej kolejności, daje to oczekiwany przeze mnie wynik.
Ale rozumiem x86_64, że odczyty odbywają się w kolejności z innymi odczytami, a zapisy są wykonywane w kolejności z innymi zapisami, ale odczyty i zapisy mogą nie zachodzić w kolejności względem siebie. Co oznacza, że X1 może się zdarzyć przed R1, a nawet X1, X2, W2, R1 w takiej kolejności - wierzę. [Wydaje się to niezwykle mało prawdopodobne, ale jeśli R1 byłyby utrzymywane przez pewne problemy z pamięcią podręczną?]
Proszę: czego nie rozumiem?
Zwracam uwagę, że jeśli zmienię obciążenia / sklepy ts->ready
na memory_order_seq_cst
, kod wygenerowany dla sklepów to:
xchg %cl,(%rdi)
co jest zgodne z moim rozumieniem x86_64 i da oczekiwany rezultat.
8.2.3.3 Stores Are Not Reordered With Earlier Loads
. Więc twój kompilator poprawnie tłumaczy twój kod (jakie to zaskakujące), tak że twój kod jest faktycznie całkowicie sekwencyjny i nic ciekawego się nie dzieje jednocześnie.