2010-01-20 10 views
10

Ich weiß, dass das .NET-Speichermodell (auf dem .NET Framework; nicht kompakt/micro/silverlight/mono/xna/was-haben-Sie) garantiert, dass für bestimmte Typen (vor allem primitive Ganzzahlen und Referenzen) Operationen garantiert wurden atomar sein..NET-Speichermodell, flüchtige Variablen und Test-and-Set: Was ist garantiert?

Weiter glaube ich, dass die x86/x64 Test-und-Set-Anweisung (und Interlocked.CompareExchange) tatsächlich verweist auf den globalen Speicherort, so dass, wenn es erfolgreich ist eine andere Interlocked.CompareExchange würde den neuen Wert sehen.

Schließlich glaube ich, dass das volatile Schlüsselwort ist eine Anweisung an den Compiler zu propagieren liest & schreibt so schnell wie möglich und Operationen über diese Variable nicht neu anordnen (rechts?).

Dies führt zu einigen Fragen:

  1. oben richtig meine Überzeugungen?
  2. Interlocked.Read hat keine Überladung für int, nur für Longs (das sind 2 Wörter und werden daher normalerweise nicht atomar gelesen). Ich habe immer angenommen, dass das .NET-Speichermodell garantiert, dass der neueste Wert beim Lesen von Ints/Referenzen zu sehen ist, jedoch mit Prozessor-Caches, -Registern usw. Ich beginne zu sehen, dass dies möglicherweise nicht möglich ist. Gibt es eine Möglichkeit, die Variable erneut zu holen?
  3. Ist volatile ausreichend, um das obige Problem für Ganzzahlen und Referenzen zu lösen?
  4. auf x86/x64 kann ich davon ausgehen, dass ...

Wenn es zwei Integer sind globale Variablen x und y, die beide auf 0 initialisiert, dass, wenn ich schreibe:

x = 1; 
y = 2; 

, dass kein Faden wird sehen x = 0 und y = 2 (dh die Schreibvorgänge werden in der Reihenfolge auftreten). Ändert sich das, wenn sie volatil sind?

Antwort

6
  • Nur Lese- und Schreibvorgänge in Variablen, die maximal 32 Bit breit sind (und 64 Bit Breite auf x64-Systemen) sind atomar. All dies bedeutet, dass Sie keine int lesen und einen halbgeschriebenen Wert erhalten. Es bedeutet nicht, dass Arithmetik atomar ist.
  • Die verriegelten Operationen dienen auch als Speicherbarrieren, also ja, Interlocked.CompareExchange wird den aktualisierten Wert sehen.
  • Siehe this page. Flüchtig heißt nicht geordnet. Einige Compiler können Operationen für flüchtige Variablen nicht neu anordnen, aber die CPU kann die Reihenfolge neu festlegen. Wenn Sie verhindern möchten, dass die CPU Befehle neu anordnet, verwenden Sie eine (vollständige) Speicherschranke.
  • Das Speichermodell stellt sicher, dass Lese- und Schreibvorgänge unteilbar sind, und die Verwendung des flüchtigen Schlüsselworts stellt sicher, dass Lesevorgänge immer aus dem Speicher kommen, nicht aus einem Register. So sehen Sie den neuesten Wert. Dies liegt daran, dass x86-CPUs den Cache bei Bedarf ungültig machen werden - siehe this und this. Siehe auch InterlockedCompareExchange64 für das automatische Lesen von 64-Bit-Werten.
  • Und schließlich die letzte Frage.Die Antwort ist ein Thread könnte tatsächlich x = 0 und y = 2 sehen, und die Verwendung des flüchtigen Schlüsselwortes ändert das nicht, weil die CPU frei ist, Befehle neu zu ordnen. Sie brauchen eine Speicherbarriere.

Zusammenfassung:

  1. Der Compiler ist frei Anweisungen neu zu ordnen.
  2. Die CPU kann Befehle neu ordnen.
  3. Lese- und Schreibvorgänge in Wordgröße sind atomar. Arithmetische und andere Operationen sind nicht atomar, weil sie eine Lese-, Rechen- und Schreiboperation beinhalten.
  4. Lesevorgänge in Word-Größe aus dem Speicher rufen immer den neuesten Wert ab. Aber die meiste Zeit weißt du nicht, ob du tatsächlich aus dem Gedächtnis liest.
  5. Eine vollständige Speichersperre stoppt (1) und (2). Bei den meisten Compilern können Sie (1) selbst stoppen.
  6. Das flüchtige Schlüsselwort stellt sicher, dass Sie aus dem Speicher lesen - (4).
  7. Die verriegelten Operationen (das Sperrpräfix) ermöglichen mehrere atomare Operationen. Zum Beispiel, lesen + schreiben (InterlockedExchange). Oder lesen + vergleichen + schreiben (InterlockedCompareExchange). Sie fungieren auch als Speicherbarrieren, so dass (1) und (2) gestoppt werden. Sie schreiben immer (natürlich) in das Gedächtnis, so dass (4) gewährleistet ist.
+2

Das Problem mit der Verknüpfung von "C Keyword Myths Zerstreut" in etwas über .NET ist, dass eine Hauptquelle von Mythen über "volatile" ist, dass Menschen so tun, als wäre es das gleiche in C, C# und Java. In C# 'volatile' gibt es tatsächlich eine Ordnungssemantik wie in http://msdn.microsoft.com/en-us/library/aa645755%28v=VS.71%29.aspx. –

+0

"Thread könnte tatsächlich x = 0 und y = 2" - seit .NET 2.0 Schreibvorgänge können nicht neu geordnet werden. – Vlad

0

Nein, das flüchtige Schlüsselwort und die Atomaritätsgarantie sind viel zu schwach. Sie brauchen eine Speicherbarriere, um das sicherzustellen. Sie können einen explizit mit der Thread.MemoryBarrier() -Methode abrufen.

+0

Okay ... das beantwortet 2 und 3, aber was ist mit # 4 (Reihenfolge, in der Threads Daten sehen werden)? –

+0

Auch von: http://msdn.microsoft.com/en-us/library/x13ttww7%28VS.71%29.aspx (lol msdn urls): "Das System liest immer den aktuellen Wert eines flüchtigen Objekts an dem Punkt Es wird angefordert, auch wenn der vorherige Befehl einen Wert vom selben Objekt angefordert hat.Auch der Wert des Objekts wird sofort bei der Zuweisung geschrieben.Der flüchtige Modifikator wird normalerweise für ein Feld verwendet, auf das mehrere Threads zugreifen, ohne die Sperre zu verwenden Anweisung, um den Zugriff zu serialisieren. Die Verwendung des Modifizierers volatile stellt sicher, dass ein Thread den aktuellsten Wert eines anderen Threads abruft. " –

+0

Oh und der Beispielcode in http://msdn.microsoft.com/de-us/library/aa645755% 28VS.71% 29.aspx - Dies scheint Ihrer Antwort zu widersprechen. –

2

Kam über diesen alten Thread. Die Antworten von Hans und wj32 sind alle korrekt, mit Ausnahme des Teils bezüglich volatile.

Insbesondere Deine Frage zu

auf x86/x64 kann ich davon ausgehen, dass ... Wenn gibt es zwei globale Integer-Variablen x und y, die beide, dass ich schreiben, wenn auf 0 initialisiert: x = 1; y = 2;

Dieser NO Thread wird x = 0 und y = 2 (dh die Schreibvorgänge werden in der Reihenfolge auftreten). Ändert sich dies, wenn sie flüchtig sind?

Wenn y flüchtig ist, ist das Schreiben in x Garantie geschehen, bevor der Schreibvorgang auf y daher wird kein Thread jemals x = 0 und y = 2 sehen. Das liegt daran, dass das Schreiben in eine flüchtige Variable die "Freigabesemantik" (logisch äquivalent zu der Emission eines Freisetzungszauns) aufweist, d. H. Alle Lese-/Schreibbefehle, bevor sie sich nicht bewegen, passiert sie. (Dies bedeutet, dass wenn x flüchtig ist, aber y nicht, Sie möglicherweise immer noch die unerwarteten x = 0 und y = 2 sehen.) Siehe die Beschreibung & Codebeispiel in der C# spec für weitere Details.