Ich habe mehrere Artikel, Vorträge und Fragen zum Stackoverflow angehört und gelesen std::atomic
und möchte sichergehen, dass ich sie gut verstanden habe. Weil ich immer noch ein bisschen verwirrt bin mit der Sichtbarkeit von Cache-Zeilen-Schreibvorgängen aufgrund möglicher Verzögerungen bei MESI-Cache-Kohärenzprotokollen (oder abgeleiteten Protokollen), Speicherpuffern, ungültigen Warteschlangen usw.
Ich habe gelesen, dass x86 ein stärkeres Speichermodell hat und dass x86 gestartete Vorgänge zurücksetzen kann, wenn sich eine Cache-Ungültigmachung verzögert. Jetzt interessiert mich aber nur noch, was ich als C ++ - Programmierer unabhängig von der Plattform annehmen soll.
[T1: Thread1 T2: Thread2 V1: gemeinsame atomare Variable]
Ich verstehe, dass std :: atomic dies garantiert,
(1) Für eine Variable treten keine Datenrennen auf (dank des exklusiven Zugriffs auf die Cache-Zeile).
(2) Je nachdem, welche Speicherreihenfolge wir verwenden, wird (mit Barrieren) garantiert, dass eine sequentielle Konsistenz auftritt (vor einer Barriere, nach einer Barriere oder beidem).
(3) Nach einem atomaren Schreiben (V1) auf T1 ist ein atomares RMW (V1) auf T2 kohärent (seine Cache-Zeile wurde mit dem geschriebenen Wert auf T1 aktualisiert).
Aber als Cache-Kohärenz-Primer erwähnen,
All diese Dinge haben zur Folge, dass beim Laden standardmäßig veraltete Daten abgerufen werden können (wenn sich eine entsprechende Ungültigkeitsanforderung in der Ungültigkeitswarteschlange befand).
Ist das Folgende richtig?
(4) std::atomic
garantiert NICHT, dass T2 bei einem atomaren Lesevorgang (V) nach einem atomaren Schreibvorgang (V) bei T1 keinen "veralteten" Wert liest.
Fragen, ob (4) richtig ist: Wenn der atomare Schreibvorgang auf T1 die Cache-Zeile ungeachtet der Verzögerung ungültig macht, warum wartet T2 darauf, dass die Ungültigmachung wirksam wird, wenn eine atomare RMW-Operation ausgeführt wird, jedoch nicht bei einem atomaren Lesevorgang?
Fragen, wenn (4) falsch ist: Wann kann ein Thread einen 'veralteten' Wert lesen und "es ist sichtbar" in der Ausführung?
Ich schätze Ihre Antworten sehr
Update 1
Es scheint also, dass ich mich damals in (3) geirrt habe. Stellen Sie sich die folgende Verschachtelung für eine anfängliche V1 = 0 vor:
T1: W(1)
T2: R(0) M(++) W(1)
Obwohl das RMW von T2 in diesem Fall garantiert vollständig nach W (1) auftritt, kann es dennoch einen "veralteten" Wert lesen (ich habe mich geirrt). Demnach garantiert Atomic keine vollständige Cache-Kohärenz, sondern nur sequentielle Konsistenz.
Update 2
(5) Stellen Sie sich nun dieses Beispiel vor (x = y = 0 und atomar):
T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");
Laut dem, was wir gesprochen haben, würde uns das Anzeigen der "Nachricht" auf dem Bildschirm keine Informationen darüber geben, dass T2 nach T1 ausgeführt wurde. Eine der folgenden Hinrichtungen könnte also stattgefunden haben:
- T1 <T3 <T2
- T1 <T2 <T3 (wobei T3 x = 1 sieht, aber noch nicht y = 1)
ist das richtig?
(6) Wenn ein Thread immer "veraltete" Werte lesen kann, was würde passieren, wenn wir das typische "Veröffentlichen" -Szenario verwenden, aber anstatt zu signalisieren, dass einige Daten bereit sind, machen wir genau das Gegenteil (löschen Sie die Daten)?
T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();
Dabei würde T2 immer noch einen gelöschten ptr verwenden, bis festgestellt wird, dass is_enabled false ist.
(7) Auch die Tatsache, dass Threads "veraltete" Werte lesen können, bedeutet, dass ein Mutex nicht mit nur einem sperrenfreien Atom implementiert werden kann, oder? Es würde einen Synchronisationsmechanismus zwischen Threads erfordern. Würde es ein abschließbares Atom erfordern?
Bezüglich (3) - hängt es von der verwendeten Speicherreihenfolge ab. Wenn sowohl das Geschäft als auch die RMW-Operation verwendet werden
std::memory_order_seq_cst
, werden beide Operationen auf irgendeine Weise angeordnet - dh entweder das Geschäft findet vor dem RMW statt oder umgekehrt. Wenn das Geschäft vor dem RMW bestellt wird, wird garantiert, dass die RMW-Operation den gespeicherten Wert "sieht". Wenn das Geschäft nach dem RMW bestellt wird, wird der von der RMW-Operation geschriebene Wert überschrieben.Wenn Sie entspanntere Speicherreihenfolgen verwenden, werden die Änderungen weiterhin in irgendeiner Weise angeordnet (die Änderungsreihenfolge der Variablen), Sie können jedoch nicht garantieren, ob der RMW den Wert aus der Speicheroperation "sieht" - selbst wenn die RMW-Operation ausgeführt wird ist die Reihenfolge nach dem Schreiben in der Änderungsreihenfolge der Variablen.
Falls Sie noch einen Artikel lesen möchten, verweise ich Sie auf Speichermodelle für C / C ++ - Programmierer .
quelle