Ich habe Probleme mit Abschnitt 5.1.2.4 des C11-Standards, insbesondere mit der Semantik von Release / Acquire. Ich stelle fest, dass https://preshing.com/20120913/acquire-and-release-semantics/ (unter anderem) besagt, dass:
... Die Release-Semantik verhindert, dass die Schreibfreigabe mit einer Lese- oder Schreiboperation, die ihr in der Programmreihenfolge vorausgeht, im Speicher neu angeordnet wird.
Also für Folgendes:
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 ;
}
wo diese ausgeführt werden:
> 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) ;
Ich würde daher erwarten, dass Thread "1" r1 == 1 und Thread "2" r2 = 4 hat.
Ich würde das erwarten, weil (gemäß den Absätzen 16 und 18 von Abschnitt 5.1.2.4):
- Alle (nicht atomaren) Lese- und Schreibvorgänge werden "vor" sequenziert und daher "vor" dem atomaren Schreiben / Freigeben in Thread "1" durchgeführt.
- welches "Inter-Thread-passiert-vor" dem atomaren Lesen / Erfassen in Thread "2" (wenn es "wahr" lautet),
- was wiederum "vorher sequenziert" ist und daher "vor" dem (nicht atomaren) Lesen und Schreiben (im Thread "2") geschieht.
Es ist jedoch durchaus möglich, dass ich den Standard nicht verstanden habe.
Ich stelle fest, dass der für x86_64 generierte Code Folgendes enthält:
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
Und vorausgesetzt, dass R1 und X1 in dieser Reihenfolge auftreten, ergibt dies das erwartete Ergebnis.
Mein Verständnis von x86_64 ist jedoch, dass Lesevorgänge in der Reihenfolge mit anderen Lese- und Schreibvorgängen in der Reihenfolge mit anderen Schreibvorgängen erfolgen, Lese- und Schreibvorgänge jedoch möglicherweise nicht in der richtigen Reihenfolge. Was bedeutet, dass X1 vor R1 und sogar X1, X2, W2, R1 in dieser Reihenfolge auftreten kann - glaube ich. [Dies scheint äußerst unwahrscheinlich, aber wenn R1 durch einige Cache-Probleme aufgehalten würde?]
Bitte: Was verstehe ich nicht?
Ich stelle fest, dass, wenn ich die Ladevorgänge / Speicher von ts->ready
in ändere memory_order_seq_cst
, der für die Speicher generierte Code lautet:
xchg %cl,(%rdi)
Das stimmt mit meinem Verständnis von x86_64 überein und wird das erwartete Ergebnis liefern.
8.2.3.3 Stores Are Not Reordered With Earlier Loads
. Ihr Compiler übersetzt Ihren Code also korrekt (wie überraschend), sodass Ihr Code effektiv vollständig sequentiell ist und nichts Interessantes gleichzeitig passiert.