Der Ausdruck "passiert stark vor" wird im C ++ - Entwurfsstandard mehrmals verwendet.
Zum Beispiel: Kündigung [basic.start.term] / 5
Wenn der Abschluss der Initialisierung eines Objekts mit statischer Speicherdauer stark vor einem Aufruf von std :: atexit erfolgt (siehe [support.start.term]), wird der Aufruf der Funktion an std :: atexit übergeben wird vor dem Aufruf des Destruktors für das Objekt sequenziert. Wenn ein Aufruf von std :: atexit stark vor Abschluss der Initialisierung eines Objekts mit statischer Speicherdauer erfolgt, wird der Aufruf des Destruktors für das Objekt vor dem Aufruf der an std :: atexit übergebenen Funktion sequenziert . Wenn ein Aufruf von std :: atexit stark vor einem weiteren Aufruf von std :: atexit erfolgt, wird der Aufruf der an den zweiten std :: atexit-Aufruf übergebenen Funktion vor dem Aufruf der an den übergebenen Funktion sequenziert erster std :: atexit Aufruf.
Und definiert in Daten Rennen [intro.races] / 12
Eine Bewertung A erfolgt stark vor einer Bewertung D, wenn auch nicht
(12.1) A wird vor D oder sequenziert
(12.2) A synchronisiert mit D und sowohl A als auch D sind sequentiell konsistente atomare Operationen ([atomics.order]) oder
(12.3) Es gibt Bewertungen B und C, so dass A vor B sequenziert wird, B einfach vor C erfolgt und C vor D sequenziert wird, oder
(12.4) Es gibt eine Bewertung B, so dass A stark vor B und B stark vor D auftritt.
[Hinweis: Wenn A informell stark vor B auftritt, scheint A in allen Kontexten vor B bewertet zu werden. Tritt stark auf, bevor Verbrauchsvorgänge ausgeschlossen werden. - Endnote]
Warum wurde "stark passiert vorher" eingeführt? Was ist intuitiv der Unterschied und die Beziehung zu "passiert vorher"?
Was bedeutet das "A scheint in allen Kontexten vor B bewertet zu werden" in der Notiz?
(Hinweis: Die Motivation für diese Frage sind die Kommentare von Peter Cordes unter dieser Antwort .)
Zusätzlicher Entwurf eines Standardzitats (danke an Peter Cordes)
Ordnung und Konsistenz [atomics.order] / 4
Für alle Operationen memory_order :: seq_cst, einschließlich Zäune, gibt es eine einzige Gesamtreihenfolge S, die die folgenden Einschränkungen erfüllt. Erstens, wenn A und B memory_order :: seq_cst-Operationen sind und A stark vor B auftritt, dann steht A in S vor B. Zweitens für jedes Paar atomarer Operationen A und B an einem Objekt M, bei dem A kohärent geordnet ist vor B müssen die folgenden vier Bedingungen von S erfüllt sein:
(4.1) Wenn A und B beide Operationen memory_order :: seq_cst sind, steht A in S vor B; und
(4.2) Wenn A eine Operation memory_order :: seq_cst ist und B vor einem Zaun Y von memory_order :: seq_cst auftritt, steht A in S vor Y; und
(4.3) Wenn ein memory_order :: seq_cst-Zaun X vor A auftritt und B eine memory_order :: seq_cst-Operation ist, steht X in S vor B; und
(4.4) Wenn ein memory_order :: seq_cst-Zaun X vor A und B vor einem memory_order :: seq_cst-Zaun Y auftritt, steht X in S vor Y.
quelle
seq_cst
, die in Atomics 31.4 Reihenfolge und Konsistenz gilt: 4 . Dies ist nicht im C ++ 17 n4659- Standard enthalten, in dem 32.4 - 3 das Vorhandensein einer einzelnen Gesamtreihenfolge von seq_cst-Operationen definiert, die mit der Reihenfolge "Vorher passiert" und den Änderungsreihenfolgen für alle betroffenen Standorte übereinstimmt . Das "stark" wurde in einem späteren Entwurf hinzugefügt.atexit()
einen Thread und einenexit()
anderen aufruft , reicht es für Initialisierer nicht aus, nur eine verbrauchsabhängige Abhängigkeit zu tragen, da sich die Ergebnisse von denen unterscheiden,exit()
die von demselben Thread aufgerufen wurden. Eine ältere Antwort von mir betraf diesen Unterschied.exit()
. Jeder Thread kann das gesamte Programm durch Beenden beenden, oder der Hauptthread kann durch Beenden beendet werdenreturn
. Dies führt zum Aufruf vonatexit()
Handlern und zum Tod aller Threads, was auch immer sie taten.Antworten:
Machen Sie sich auch auf "einfach passiert vorher" gefasst! Sehen Sie sich diesen aktuellen Schnappschuss von cppref https://en.cppreference.com/w/cpp/atomic/memory_order an
Es scheint, dass in C ++ 20 "einfach vorher passiert" hinzugefügt wurde.
Simply-HB und HB sind also gleich, außer wie sie mit Verbrauchsvorgängen umgehen. Siehe HB
Wie unterscheiden sie sich hinsichtlich des Verbrauchs? Siehe Inter-Thread-HB
Eine Operation, die abhängig geordnet ist (dh Release / Consum verwendet), ist HB, aber nicht unbedingt Simply-HB.
Konsumieren ist entspannter als erwerben. Wenn ich das richtig verstehe, ist HB entspannter als Simply-HB.
Ein Release / Consum-Vorgang kann also nicht Strongly-HB sein.
Release / Acquisition kann HB und Simply-HB sein (da Release / Acquisition mit synchronisiert ist), ist aber nicht unbedingt Strongly-HB. Weil Strongly-HB ausdrücklich sagt, dass A mit B synchronisieren muss UND eine sequentiell konsistente Operation sein muss.
Alle Kontexte: Alle Threads / alle CPUs sehen (oder "werden sich irgendwann einig") dieselbe Reihenfolge. Dies ist die Garantie für die sequentielle Konsistenz - eine globale Gesamtmodifikationsreihenfolge aller Variablen. Erfassungs- / Freigabeketten garantieren nur die wahrgenommene Änderungsreihenfolge für an der Kette beteiligte Threads. Gewinde außerhalb der Kette dürfen theoretisch eine andere Reihenfolge sehen.
Ich weiß nicht, warum Strongly-HB und Simply-HB eingeführt wurden. Vielleicht, um zu klären, wie man mit Konsum umgeht? Stark-HB hat nette Eigenschaften - wenn ein Thread A stark beobachtet - bevor B passiert, weiß er, dass alle Threads dasselbe beobachten.
Die Geschichte des Konsums:
Paul E. McKenney ist dafür verantwortlich, dass der Konsum den C- und C ++ - Standards entspricht. Consume garantiert die Reihenfolge zwischen der Zeigerzuweisung und dem Speicher, auf den es zeigt. Es wurde wegen des DEC Alpha erfunden. Das DEC Alpha konnte einen Zeiger spekulativ dereferenzieren, daher hatte es auch einen Speicherzaun, um dies zu verhindern. Der DEC Alpha wird nicht mehr hergestellt und keine Prozessoren haben heute dieses Verhalten. Konsumieren soll sehr entspannt sein.
quelle
mo_consume
soll die Reihenfolge der Datenabhängigkeit auf realen CPUs nutzen und formalisieren, dass der Compiler die Datenabhängigkeit nicht über die Verzweigungsvorhersage aufheben kann. zBint *p = load();
tmp = *p;
könnte die Einführung durch den Compiler gebrochen werden ,if(p==known_address) tmp = *known_address; else tmp=*p;
wenn sie aus irgendeinem Grund haben ein gewisser Zeigerwert zu erwarten üblich zu sein. Das ist legal für entspannt, aber nicht konsumieren.