Ineinandergreifend und flüchtig

75

Ich habe eine Variable, mit der ich den Zustand darstelle. Es kann aus mehreren Threads gelesen und beschrieben werden.

Ich benutze Interlocked.Exchangeund Interlocked.CompareExchangeum es zu ändern. Ich lese es jedoch aus mehreren Threads.

Ich weiß, dass volatiledies verwendet werden kann, um sicherzustellen, dass die Variable nicht lokal zwischengespeichert wird, sondern immer direkt aus dem Speicher liest.

Wenn ich die Variable jedoch auf flüchtig setze, wird eine Warnung über die Verwendung von flüchtig und die Übergabe mit ref an die Interlocked-Methoden generiert.

Ich möchte sicherstellen, dass jeder Thread den neuesten Wert der Variablen liest und nicht eine zwischengespeicherte Version, aber ich kann flüchtig nicht verwenden.

Es gibt ein, Interlocked.Read aber es ist für 64-Bit-Typen und ist auf dem kompakten Framework nicht verfügbar. Die Dokumentation dazu besagt, dass es für 32-Bit-Typen nicht benötigt wird, da sie bereits in einer einzelnen Operation ausgeführt werden.

Über das Internet werden Aussagen gemacht, dass Sie nicht flüchtig sind, wenn Sie die Interlocked-Methoden für Ihren gesamten Zugriff verwenden. Sie können jedoch keine 32-Bit-Variable mit den Interlocked-Methoden lesen, sodass Sie Interlocked-Methoden auf keinen Fall für Ihren gesamten Zugriff verwenden können.

Gibt es eine Möglichkeit, das thread-sichere Lesen und Schreiben meiner Variablen ohne Verwendung der Sperre zu erreichen?

Trampster
quelle
2
Gute Frage eigentlich. Die Verwendung einer regulären Sperre impliziert einen kritischen Ausführungspunkt und garantiert, dass Ihr Wert für alle Threads auf dem neuesten Stand ist. Interlocked.Exchange wird jedoch nicht mit implementiert, lockund ich kann keine Referenz finden, die solche Garantien gibt.
Thorarin

Antworten:

45

Sie können diese Warnung ignorieren, wenn Sie Interlocked.XxxFunktionen verwenden (siehe diese Frage ), da diese immer flüchtige Operationen ausführen. Eine volatileVariable ist also für den gemeinsam genutzten Status vollkommen in Ordnung. Wenn Sie um jeden Preis loswerden der Warnung möchten, können Sie tatsächlich kann eine verriegelte Lese tun mit Interlocked.CompareExchange (ref counter, 0, 0).

Bearbeiten: Tatsächlich benötigen Sie volatileIhre Statusvariable nur, wenn Sie direkt darauf schreiben möchten (dh nicht verwenden Interlocked.Xxx). Wie von jerryjvl erwähnt , wird beim Lesen einer Variablen, die mit einer ineinandergreifenden (oder flüchtigen) Operation aktualisiert wurde, der neueste Wert verwendet.

Anton Tykhyy
quelle
Wo soll ich 0 durch einen Wert ersetzen, der meine Variable niemals sein wird?
Trampster
3
Puh, wenn es nicht so wäre, wäre Interlocked ziemlich nutzlos. Hat mich dort für eine Minute
erschreckt
@ Daniel: Nein, die konstant spielt keine Rolle, da CompareExchangewird ersetzt werden , 0mit , 0wenn counterist 0- dh ein No-op.
Anton Tykhyy
2
Sind Lesevorgänge einer nicht volatilevariablen Person vor einer Optimierung durch den Compiler oder die JIT geschützt? Ist die Bearbeitung, die sich auf @jerryjvl bezieht, nicht falsch?
Binki
44

Interlocked Operations und Volatile sollten eigentlich nicht gleichzeitig verwendet werden. Der Grund, warum Sie eine Warnung erhalten, ist, dass sie (fast?) Immer anzeigt, dass Sie falsch verstanden haben, was Sie tun.

Übermäßiges Vereinfachen und Paraphrasieren:
volatileGibt an, dass jeder Lesevorgang erneut aus dem Speicher gelesen werden muss, da möglicherweise andere Threads die Variable aktualisieren. Wenn dies auf ein Feld angewendet wird, das von der Architektur, auf der Sie ausgeführt werden, atomar gelesen / geschrieben werden kann, sollte dies alles sein, was Sie tun müssen, es sei denn, Sie verwenden long / ulong. Die meisten anderen Typen können atomar gelesen / geschrieben werden.

Wenn ein Feld nicht als flüchtig markiert ist, können Sie InterlockedOperationen verwenden, um eine ähnliche Garantie zu geben, da dadurch der Cache geleert wird, sodass das Update für alle anderen Prozessoren sichtbar ist. Dies hat den Vorteil, dass Sie den Overhead übernehmen das Update eher als das Lesen.

Welcher dieser beiden Ansätze am besten funktioniert, hängt davon ab, was genau Sie tun. Und diese Erklärung ist eine grobe Übervereinfachung. Daraus sollte jedoch klar sein, dass es sinnlos ist, beides gleichzeitig zu tun.

jerryjvl
quelle
2
Es ist an sich nichts Falsches daran, flüchtig und Interlocked zusammen zu verwenden. Der Grund, warum Sie eine Warnung erhalten, ist, volatiledass dies volatilekein Teil des Typsystems ist und der Angerufene, der die Referenz erhält, nicht weiß, dass die referenzierte Variable einen flüchtigen Zugriff benötigt.
Anton Tykhyy
2
Für die Aufzeichnung gibt es hier Diskussion, wo vorgeschlagen wird, dass diese Antwort falsch ist. @ Jerryjvl, wenn Sie dazu beitragen möchten, wäre das großartig.
Roman Starkov
2
Darin steckt mehr Subtilität, als ich beim ersten Schreiben dieser Antwort festgestellt hatte. Ich empfehle dringend, Joe Duffy (und / oder sein Buch über Parallelität) zu lesen, um ein detailliertes Verständnis im Zusammenhang mit dem angesprochenen Problem zu erhalten. Er ist wirklich die Autorität, wenn es um diese Angelegenheiten geht. Das große Problem bei Lock-Free ist, dass es normalerweise in gewissem Sinne um Leistung geht, aber es gibt Fallstricke auf dem Weg, die entweder zu falschen Ergebnissen führen oder die angestrebte Leistung beeinträchtigen können.
Jerryjvl
9
Joe Duffy spricht in diesem Blogeintrag speziell über den Verweis auf flüchtige / verriegelte Operationen . Das Mischen von ineinandergreifenden Referenzen ist zu 100% in Ordnung, und er hat beantragt, dem C # -Compiler einen Sonderfall hinzuzufügen, um in diesem Fall keine Warnung auszulösen.
Chuu