Ich habe gerade Herb Sutter Vortrag gesehen: C ++ und darüber hinaus 2012: Herb Sutter - atomare & lt; & gt; Waffen, 2 von 2
Er zeigt einen Fehler bei der Implementierung von std :: shared_ptr destructor:
%Vor%Er sagt, dass memory_order_relaxed gelöscht werden kann, bevor fetch_sub.
Um 1:25:18 - Release hält Zeile B nicht unten, wo es sein sollte
Wie ist das möglich? Vor der Beziehung gibt es eine Situation vor / sequenziert, weil sie beide in einem einzelnen Thread sind. Ich könnte mich irren, aber es gibt auch eine Abhängigkeit zwischen fetch_sub und delete.
Wenn er Recht hat, welche ISO-Elemente unterstützen das?
Stellen Sie sich einen Code vor, der einen gemeinsamen Zeiger freigibt:
%Vor%Wenn dec_ref () keine "release" -Semantik hat, ist es völlig in Ordnung, wenn ein Compiler (oder eine CPU) Dinge von vor dec_ref () zu nachher (zB) bewegt:
%Vor%Und das ist nicht sicher, da dec_ref () auch von anderen Threads gleichzeitig aufgerufen werden kann und das Objekt löscht. Also muss es eine "release" -Semantik für Dinge vor dec_ref () haben, um dort zu bleiben.
Stellen wir uns nun vor, dass der Destruktor des Objekts so aussieht:
%Vor%Auch wir werden das Beispiel ein bisschen ändern und werden 2 Threads haben:
%Vor%Dann wird der "aggregierte" Code wie folgt aussehen:
%Vor%Wenn wir jedoch nur eine "release" -Semantik für atomic_sub () haben, kann dieser Code auf folgende Weise optimiert werden:
%Vor%Aber so wird der Destruktor nicht immer den letzten Wert von "a" ausgeben (dieser Code ist nicht mehr rennfrei). Deshalb brauchen wir auch eine Semantik für atomic_sub (oder genau genommen brauchen wir eine Acquir-Barriere, wenn der Zähler nach Dekrement 0 wird).
Im Gespräch zeigt Herb memory_order_release
nicht memory_order_relaxed
, aber entspannt hätte noch mehr Probleme.
Wenn delete control_block_ptr
auf control_block_ptr->refs
zugreift (was wahrscheinlich nicht der Fall ist), führt die atomare Operation keine Abhängigkeit zum Löschen. Die Löschoperation berührt möglicherweise keinen Speicher im Steuerblock, sondern gibt den Zeiger nur an den Freestore-Allokator zurück.
Aber ich bin mir nicht sicher, ob Herb darüber spricht, dass der Compiler das Löschen vor der atomaren Operation verschiebt oder sich nur darauf bezieht, wenn die Nebenwirkungen für andere Threads sichtbar werden.
Sieht aus, als würde er über die Synchronisierung von Aktionen auf dem gemeinsamen Objekt selbst sprechen, die nicht auf seinen Code-Blöcken angezeigt werden (und als Ergebnis - verwirrend).
Deshalb hat er acq_rel
gesetzt - weil alle Aktionen auf dem Objekt vor seiner Zerstörung stattfinden sollen, alles in der richtigen Reihenfolge.
Aber ich bin mir immer noch nicht sicher, warum er über den Austausch von delete
mit fetch_sub
redet.
Dies ist eine späte Antwort.
Beginnen wir mit diesem einfachen Typ:
%Vor% Und wir verwenden diesen Typ in shared_ptr
wie folgt:
Zwei Threads werden parallel ausgeführt, wobei beide den Besitz eines foo
-Objekts teilen.
Mit einer korrekten shared_ptr
Implementierung
(also eins mit memory_order_acq_rel
), dieses Programm hat ein definiertes Verhalten.
Der einzige Wert, der von diesem Programm ausgegeben wird, ist 5
.
Bei einer falschen Implementierung (mit memory_order_relaxed
)
sind keine solchen Garantien. Das Verhalten ist nicht definiert, da ein Datenrennen von
foo::value
wird eingeführt. Das Problem tritt nur für Fälle auf, wenn der Destruktor
wird im Hauptthread aufgerufen. Mit einer entspannten Speicherordnung, dem Schreiben
to foo::value
im anderen Thread wird möglicherweise nicht an den Destruktor im Hauptthread weitergegeben.
Ein anderer Wert als 5
könnte gedruckt werden.
Was ist ein Datenrennen? Nun, schauen Sie sich die Definition an und achten Sie auf den letzten Punkt:
Wenn eine Auswertung eines Ausdrucks in einen Speicherort schreibt und eine andere Auswertung den gleichen Speicherort liest oder ändert, wird gesagt, dass die Ausdrücke in Konflikt stehen. Ein Programm, das zwei widersprüchliche Bewertungen hat, hat ein Datenrennen, es sei denn
- beide widersprüchlichen Bewertungen sind atomare Operationen (siehe std :: atomic)
- eine der widersprüchlichen Auswertungen passiert - vor einer anderen (siehe std :: memory_order)
In unserem Programm schreibt ein Thread in foo::value
und ein Thread wird
Lese von foo::value
. Diese sollen sequentiell sein; das Schreiben
to foo::value
sollte immer vor dem Lesen passieren. Intuitiv
macht Sinn, dass sie so sein würden, wie der Destruktor der letzte sein soll
was passiert mit einem Objekt.
memory_order_relaxed
bietet jedoch keine solchen Bestellgarantien und daher wird memory_order_acq_rel
benötigt.
Tags und Links c++ c++11 lock-free atomic memory-model