Was bedeutet "stark vorher passiert"?

9

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.

Neugieriger
quelle
1
Der aktuelle Standardentwurf bezieht sich auch auf "A tritt stark vor B auf" als Bedingung für eine Regel 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.
Peter Cordes
2
@PeterCordes Ich denke, der Kommentar ohne Konsum, der besagt, dass es HB "in allen Kontexten" / "stark" ist und über Aufrufe von Funktionszeigern spricht, ist so etwas wie ein totes Werbegeschenk. Wenn ein Multithread-Programm atexit()einen Thread und einen exit()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.
Iwillnotexist Idonotexist
@IwillnotexistIdonotexist Kannst du überhaupt ein MT-Programm beenden? Ist es nicht grundsätzlich eine kaputte Idee?
Neugieriger
1
@curiousguy Das ist der Zweck von exit(). Jeder Thread kann das gesamte Programm durch Beenden beenden, oder der Hauptthread kann durch Beenden beendet werden return. Dies führt zum Aufruf von atexit()Handlern und zum Tod aller Threads, was auch immer sie taten.
Iwillnotexist Idonotexist

Antworten:

5

Warum wurde "stark passiert vorher" eingeführt? Was ist intuitiv der Unterschied und die Beziehung zu "passiert vorher"?

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

Geben Sie hier die Bildbeschreibung ein

Es scheint, dass in C ++ 20 "einfach vorher passiert" hinzugefügt wurde.

Einfach passiert - vorher

Unabhängig von den Threads erfolgt die Bewertung A einfach vor der Bewertung B, wenn eine der folgenden Bedingungen erfüllt ist:

1) A wird vor B sequenziert

2) A synchronisiert mit B.

3) A passiert einfach vor X und X passiert einfach vor B.

Hinweis: Ohne Konsumvorgänge sind die Beziehungen einfach vor und vor den Beziehungen gleich.

Simply-HB und HB sind also gleich, außer wie sie mit Verbrauchsvorgängen umgehen. Siehe HB

Passiert vorher

Unabhängig von den Threads erfolgt die Bewertung A vor der Bewertung B, wenn eine der folgenden Bedingungen erfüllt ist:

1) A wird vor B sequenziert

2) Ein Inter-Thread findet vor B statt

Die Implementierung ist erforderlich, um sicherzustellen, dass die Vor-Vor-Beziehung azyklisch ist, indem bei Bedarf eine zusätzliche Synchronisation eingeführt wird (dies kann nur erforderlich sein, wenn eine Verbrauchsoperation beteiligt ist, siehe Batty et al.).

Wie unterscheiden sie sich hinsichtlich des Verbrauchs? Siehe Inter-Thread-HB

Inter-Thread passiert vorher

Zwischen den Threads erfolgt die Bewertung A zwischen den Threads vor der Bewertung B, wenn eine der folgenden Aussagen zutrifft

1) A synchronisiert mit B.

2) A ist abhängig von B geordnet

3) ...

...

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.

Stark passiert-vorher

Unabhängig von den Threads erfolgt die Bewertung A stark vor der Bewertung B, wenn eine der folgenden Bedingungen erfüllt ist:

1) A wird vor B sequenziert

2) A synchronisiert sich mit B und sowohl A als auch B sind sequentiell konsistente atomare Operationen

3) A wird vor X sequenziert, X passiert einfach vor Y und Y wird vor B sequenziert

4) A passiert stark - vor X und X passiert stark - vor B.

Hinweis: Informell scheint A in allen Kontexten vor B ausgewertet zu werden, wenn A stark vor B auftritt.

Hinweis: Dies geschieht stark, bevor Verbrauchsvorgänge ausgeschlossen werden.

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.

                            Is happens-before guaranteed?

                        HB             Simply-HB          Strongly-HB

relaxed                 no                 no                 no
release/consume        yes                 no                 no      
release/acquire        yes                yes                 no
S.C.                   yes                yes                yes

Was bedeutet das "A scheint in allen Kontexten vor B bewertet zu werden" in der Notiz?

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.

Humphrey Winnebago
quelle
1
Guter Kummer. Ich bereue es fast, diese Frage gestellt zu haben. Ich möchte noch einmal auf die einfachen C ++ - Probleme wie die Regeln der Iterator-Ungültigmachung, die argumentabhängige Namenssuche, benutzerdefinierte Konvertierungsoperatoren für Vorlagen, die Ableitung von Vorlagenargumenten, die Suche nach Namen in einer Basisklasse in einem Mitglied einer Vorlage und Ihre Probleme eingehen kann zu Beginn der Konstruktion eines Objekts in eine virtuelle Basis konvertiert werden.
Neugieriger
Re: verbrauchen. Behaupten Sie, dass das Schicksal der Konsumbestellung mit dem Schicksal von DEC Alpha zusammenhängt und außerhalb dieses bestimmten Bogens keinen Wert hat?
Neugieriger
1
Das ist eine gute Frage. Wenn man sich das jetzt genauer ansieht, klingt es so, als könnte der Verbrauch theoretisch einen Leistungsschub für schwach geordnete Bögen wie ARM und PowerPC bringen. Gib mir noch etwas Zeit, mich damit zu beschäftigen.
Humphrey Winnebago
1
Ich würde sagen, dass Konsum aufgrund aller schwach geordneten ISAs außer Alpha existiert. In Alpha asm sind die einzigen Optionen entspannt und erwerben (und seq-cst), nicht Abhängigkeitsreihenfolge. mo_consumesoll die Reihenfolge der Datenabhängigkeit auf realen CPUs nutzen und formalisieren, dass der Compiler die Datenabhängigkeit nicht über die Verzweigungsvorhersage aufheben kann. zB int *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.
Peter Cordes
@PeterCordes richtig ... Bögen mit schwacher Ordnung müssen eine Speicherbarriere für den Erwerb ausgeben, aber (theoretisch) nicht für den Verbrauch. Es hört sich so an, als ob Sie denken, wenn das Alpha nie existiert hätte, hätten wir es immer noch konsumiert? Außerdem sagen Sie im Grunde, dass Konsum eine ausgefallene (oder "Standard") Compiler-Barriere ist.
Humphrey Winnebago