Wofür wird Interlocked.CompareExchange in der Dapper-Methode verwendet?

.net c# dapper thread-safety

Frage

In Dapper-Code in der Link.TryAdd Methode gibt es das folgende Stück Code:

var snapshot = Interlocked.CompareExchange(ref head, null, null);

Warum ist das erforderlich statt einfach:

var snapshot = head;

Beide Zeilen ändern nicht den Wert von head , beide Zeilen weisen den Wert von head dem snapshot . Warum wurde der erste über den zweiten gewählt?

Edit: der Code, den ich beziehe, ist hier: https://github.com/SamSaffron/dapper-dot-net/blob/77227781c562e65c167bf7a933d69291d5bdc6f3/Dapper/SqlMapper.cs

Akzeptierte Antwort

Sie möchten eine flüchtige Leseoperation Thread.VolatileRead , es gibt jedoch keine Überladung von Thread.VolatileRead , die einen generischen Typparameter verwendet. Wenn Sie Interlocked.CompareExchange , wird dasselbe Ergebnis erzielt.

Das Problem, das sie zu lösen versuchen, ist, dass der JIT-Compiler die Zuweisung zu einem Temp weg optimieren kann, wenn er es für richtig hält. Dies kann zu Threading-Problemen führen, wenn ein anderer Thread die Kopfreferenz verändert, während der aktuelle Thread sie in einer Sequenz von Operationen verwendet.

Bearbeiten:

Das Problem ist nicht, dass ein veralteter Wert zu Beginn von TryAdd gelesen TryAdd . Das Problem ist, dass sie in Zeile 105 den aktuellen Kopf mit dem vorherigen Kopf vergleichen müssen (im snapshot ). Wenn es eine Optimierung gibt, gibt es keine snapshot Variable, die den vorherigen Wert enthält, und der head wird an diesem Punkt erneut gelesen. Es ist sehr wahrscheinlich, dass CompareExchange erfolgreich ist, obwohl head sich möglicherweise zwischen den Zeilen 103 und 105 geändert hat. Das Ergebnis ist, dass ein Knoten in der Liste verloren geht, wenn zwei Threads TryAdd gleichzeitig TryAdd .


Beliebte Antwort

mike z hat recht: Dies verhindert eine (legale) JIT-Optimierung, die den Code durchbrechen würde.

Sie hätten jedoch den Trick der flüchtigen Struktur verwenden können: head lesen und einem flüchtigen Feld einer Struktur zuweisen. Als nächstes lesen Sie es aus diesem Feld und es ist garantiert eine flüchtige lesen!

Die Struktur selbst spielt keine Rolle. Es kommt nur darauf an, dass ein flüchtiges Feld verwendet wurde, um die Variable zu kopieren.

So wie das:

struct VolatileHelper<T> { public volatile T Value; }
...
var volatileHelper = new VolatileHelper<Field>();
volatileHelper.Value = head;
var snapshot = volatileHelper.Value;

Hoffentlich hat es keine Laufzeitkosten. In jedem Fall sind die Kosten geringer als eine verriegelte Operation, die CPU-Speicherkohärenzverkehr verursacht.

Tatsächlich macht die Tatsache, dass jeder Cache-Zugriff (sogar ein Lesezugriff) Speicherkohärenz-Verkehr erfordert, dies zu einem langsamen Cache ! Interlock-Operationen sind eine globale Systemressource, die nicht mit mehr CPUs skaliert werden kann. Ein Interlocked-Zugriff verwendet eine globale Hardwaresperre (pro Speicheradresse, aber hier gibt es nur eine Adresse).



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum