Wofür wird das Schlüsselwort "volatile" verwendet?


130

Ich habe einige Artikel über das volatileSchlüsselwort gelesen , konnte aber die korrekte Verwendung nicht herausfinden. Könnten Sie mir bitte sagen, wofür es in C # und in Java verwendet werden soll?


1
Eines der Probleme mit volatile ist, dass es mehr als eine Sache bedeutet. Die Information des Compilers, keine funky Optimierungen vorzunehmen, ist ein C-Erbe. Dies bedeutet auch , dass beim Zugriff Speicherbarrieren verwendet werden sollten. Aber in den meisten Fällen kostet es nur Leistung und / oder verwirrt die Menschen. : P
AnorZaken

Antworten:


93

Sowohl für C # als auch für Java teilt "volatile" dem Compiler mit, dass der Wert einer Variablen niemals zwischengespeichert werden darf, da sich sein Wert außerhalb des Programmbereichs selbst ändern kann. Der Compiler vermeidet dann Optimierungen, die zu Problemen führen können, wenn sich die Variable "außerhalb ihrer Kontrolle" ändert.


@ Tom - ordnungsgemäß vermerkt, Sir - und geändert.
Will A

11
Es ist immer noch viel subtiler.
Tom Hawtin - Tackline

1
Falsch. Verhindert nicht das Caching. Siehe meine Antwort.
Doug65536

168

Betrachten Sie dieses Beispiel:

int i = 5;
System.out.println(i);

Der Compiler kann dies so optimieren, dass nur 5 gedruckt werden:

System.out.println(5);

Wenn sich jedoch ein anderer Thread ändern kann i, ist dies das falsche Verhalten. Wenn sich ein anderer Thread iin 6 ändert , wird in der optimierten Version weiterhin 5 gedruckt.

Das volatileSchlüsselwort verhindert eine solche Optimierung und Zwischenspeicherung und ist daher nützlich, wenn eine Variable von einem anderen Thread geändert werden kann.


3
Ich glaube, die Optimierung wäre weiterhin gültig mit imarkiert als volatile. In Java dreht sich alles um Beziehungen, die vor dem Auftreten stattfinden.
Tom Hawtin - Tackline

Vielen Dank für die Veröffentlichung, hat also irgendwie flüchtig Verbindungen mit variabler Sperre?
Mircea

@Mircea: Das ist es, was mir gesagt wurde, dass das Markieren von etwas als flüchtig alles bedeutet: Das Markieren eines Feldes als flüchtig würde einen internen Mechanismus verwenden, damit Threads einen konsistenten Wert für die angegebene Variable sehen können, aber dies wird in der obigen Antwort nicht erwähnt ... kann das vielleicht jemand bestätigen oder nicht? Danke
npinti

5
@ Joerd: Ich bin nicht sicher, ob ich dieses Beispiel verstehe. Wenn ies sich um eine lokale Variable handelt, kann sie ohnehin kein anderer Thread ändern. Wenn es sich um ein Feld handelt, kann der Compiler den Aufruf nur optimieren, wenn dies der Fall ist final. Ich glaube nicht, dass der Compiler Optimierungen vornehmen kann, finalwenn er davon ausgeht, dass ein Feld "aussieht", wenn es nicht explizit als solches deklariert ist.
Polygenschmierstoffe

1
C # und Java sind nicht C ++. Das ist nicht richtig. Es verhindert nicht das Caching und es verhindert nicht die Optimierung. Es geht um Lese-, Erfassungs- und Speicherfreigabesemantik, die für schwach geordnete Speicherarchitekturen erforderlich ist. Es geht um spekulative Ausführung.
Doug65536

40

Um zu verstehen, was flüchtig mit einer Variablen macht, ist es wichtig zu verstehen, was passiert, wenn die Variable nicht flüchtig ist.

  • Variable ist nichtflüchtig

Wenn zwei Threads A & B auf eine nichtflüchtige Variable zugreifen, verwaltet jeder Thread eine lokale Kopie der Variablen in seinem lokalen Cache. Änderungen, die von Thread A im lokalen Cache vorgenommen wurden, sind für Thread B nicht sichtbar.

  • Variable ist flüchtig

Wenn Variablen als flüchtig deklariert werden, bedeutet dies im Wesentlichen, dass Threads eine solche Variable nicht zwischenspeichern sollten, oder mit anderen Worten, Threads sollten den Werten dieser Variablen nicht vertrauen, es sei denn, sie werden direkt aus dem Hauptspeicher gelesen.

Wann sollte eine Variable volatil gemacht werden?

Wenn Sie eine Variable haben, auf die viele Threads zugreifen können, und Sie möchten, dass jeder Thread den neuesten aktualisierten Wert dieser Variablen erhält, auch wenn der Wert von einem anderen Thread / Prozess / außerhalb des Programms aktualisiert wird.


2
Falsch. Es hat nichts mit "Verhindern des Caching" zu tun. Es geht darum, den Compiler ODER die CPU-Hardware durch spekulative Ausführung neu zu ordnen.
Doug65536

37

Lesevorgänge flüchtiger Felder haben Semantik erlangt . Dies bedeutet, dass garantiert ist, dass der aus der flüchtigen Variablen gelesene Speicher vor allen folgenden Speicherlesevorgängen auftritt. Es hindert den Compiler daran, die Neuordnung durchzuführen, und wenn die Hardware dies erfordert (schwach geordnete CPU), verwendet es eine spezielle Anweisung, um die Hardware dazu zu bringen, alle Lesevorgänge zu leeren, die nach dem flüchtigen Lesevorgang auftreten, aber spekulativ früh gestartet wurden, oder die CPU könnte Verhindern Sie, dass sie vorzeitig ausgegeben werden, indem Sie verhindern, dass zwischen der Ausgabe des Erwerbs der Last und ihrer Stilllegung spekulative Belastungen auftreten.

Schreibvorgänge flüchtiger Felder haben eine Release-Semantik . Dies bedeutet, dass garantiert wird, dass alle Speicherschreibvorgänge in die flüchtige Variable verzögert werden, bis alle vorherigen Speicherschreibvorgänge für andere Prozessoren sichtbar sind.

Betrachten Sie das folgende Beispiel:

something.foo = new Thing();

Wenn fooes sich um eine Mitgliedsvariable in einer Klasse handelt und andere CPUs Zugriff auf die Objektinstanz haben, auf die verwiesen wird something, wird möglicherweise die Wertänderung fooangezeigt, bevor die Speicherschreibvorgänge im ThingKonstruktor global sichtbar sind! Dies ist, was "schwach geordneter Speicher" bedeutet. Dies kann auch dann auftreten, wenn der Compiler alle Speicher im Konstruktor vor dem Speicher hat foo. Wenn dies der Fall fooist, verfügt volatileder Speicher über fooeine Release-Semantik, und die Hardware garantiert, dass alle Schreibvorgänge vor dem Schreibvorgang foofür andere Prozessoren sichtbar sind, bevor der Schreibvorgang ausgeführt werden kann foo.

Wie ist es möglich, dass die Schreibvorgänge fooso schlecht nachbestellt werden? Wenn sich die Cachezeile fooim Cache befindet und die Speicher im Konstruktor den Cache verpasst haben, kann der Speicher möglicherweise viel früher abgeschlossen werden, als die Schreibvorgänge in den Cache fehlschlagen.

Die (schreckliche) Itanium-Architektur von Intel hatte Speicher schwach geordnet. Der in der ursprünglichen XBox 360 verwendete Prozessor hatte einen schwach geordneten Speicher. Viele ARM-Prozessoren, einschließlich des sehr beliebten ARMv7-A, haben einen schwach geordneten Speicher.

Entwickler sehen diese Datenrennen oft nicht, weil Dinge wie Sperren eine vollständige Speicherbarriere bilden, im Wesentlichen dasselbe wie das gleichzeitige Erfassen und Freigeben von Semantik. Es können keine Lasten innerhalb des Schlosses spekulativ ausgeführt werden, bevor das Schloss erworben wird. Sie werden verzögert, bis das Schloss erfasst wird. Während einer Sperrefreigabe können keine Speicher verzögert werden. Die Anweisung, die die Sperre aufhebt, wird verzögert, bis alle in der Sperre vorgenommenen Schreibvorgänge global sichtbar sind.

Ein vollständigeres Beispiel ist das Muster "Double-Checked Locking". Der Zweck dieses Musters besteht darin, zu vermeiden, dass immer eine Sperre erworben werden muss, um ein Objekt verzögert zu initialisieren.

Aus Wikipedia entnommen:

public class MySingleton {
    private static object myLock = new object();
    private static volatile MySingleton mySingleton = null;

    private MySingleton() {
    }

    public static MySingleton GetInstance() {
        if (mySingleton == null) { // 1st check
            lock (myLock) {
                if (mySingleton == null) { // 2nd (double) check
                    mySingleton = new MySingleton();
                    // Write-release semantics are implicitly handled by marking
                    // mySingleton with 'volatile', which inserts the necessary memory
                    // barriers between the constructor call and the write to mySingleton.
                    // The barriers created by the lock are not sufficient because
                    // the object is made visible before the lock is released.
                }
            }
        }
        // The barriers created by the lock are not sufficient because not all threads
        // will acquire the lock. A fence for read-acquire semantics is needed between
        // the test of mySingleton (above) and the use of its contents. This fence
        // is automatically inserted because mySingleton is marked as 'volatile'.
        return mySingleton;
    }
}

In diesem Beispiel sind die Speicher im MySingletonKonstruktor möglicherweise für andere Prozessoren vor dem Speicher nicht sichtbar mySingleton. In diesem Fall erhalten die anderen Threads, die einen Blick auf mySingleton werfen, keine Sperre und nehmen die Schreibvorgänge an den Konstruktor nicht unbedingt auf.

volatileverhindert niemals das Caching. Es garantiert die Reihenfolge, in der andere Prozessoren "sehen". Eine Speicherfreigabe verzögert einen Speicher, bis alle ausstehenden Schreibvorgänge abgeschlossen sind und ein Buszyklus ausgegeben wurde, der andere Prozessoren auffordert, ihre Cache-Zeile zu verwerfen / zurückzuschreiben, wenn sie zufällig die relevanten Zeilen zwischengespeichert haben. Bei einer Lasterfassung werden alle spekulierten Lesevorgänge gelöscht, um sicherzustellen, dass es sich nicht um veraltete Werte aus der Vergangenheit handelt.


Gute Erklärung. Auch ein gutes Beispiel für eine doppelte Überprüfung der Verriegelung. Ich bin mir jedoch immer noch nicht sicher, wann ich es verwenden soll, da ich mir Sorgen über die Caching-Aspekte mache. Wenn ich eine Warteschlangenimplementierung schreibe, in der nur 1 Thread schreibt und nur 1 Thread liest, kann ich dann ohne Sperren auskommen und nur meine Kopf- und Schwanz- "Zeiger" als flüchtig markieren? Ich möchte sicherstellen, dass sowohl der Leser als auch der Schreiber die aktuellsten Werte sehen.
Nickdu

Beide headund tailmüssen volatil sein, um zu verhindern, dass der Hersteller davon ausgeht, taildass sich nichts ändert, und um zu verhindern, dass der Verbraucher davon ausgeht, headdass sich nichts ändert. Auch headmuss flüchtig sein , um sicherzustellen , dass die Warteschlangendaten schreiben sind global sichtbar , bevor das Geschäft zu headglobal sichtbar ist.
Doug65536

+1, Begriffe wie "zuletzt" / "am aktuellsten" implizieren leider ein Konzept des singulären korrekten Werts. In der Realität können zwei Konkurrenten genau zur gleichen Zeit eine Ziellinie überqueren - auf einer CPU können zwei Kerne genau zur gleichen Zeit ein Schreiben anfordern . Schließlich wechseln sich Kerne bei der Arbeit nicht ab - das würde Multi-Core sinnlos machen. Gutes Multithread-Denken / -Design sollte sich nicht darauf konzentrieren, eine "Neuheit" auf niedriger Ebene zu erzwingen - von Natur aus eine Fälschung, da ein Schloss nur die Kerne dazu zwingt, willkürlich jeweils einen Lautsprecher ohne Fairness auszuwählen -, sondern versuchen, das zu entwerfen Notwendigkeit für solch ein unnatürliches Konzept.
AnorZaken

34

Das flüchtige Schlüsselwort hat sowohl in Java als auch in C # unterschiedliche Bedeutungen.

Java

Aus der Java-Sprachspezifikation :

Ein Feld kann als flüchtig deklariert werden. In diesem Fall stellt das Java-Speichermodell sicher, dass alle Threads einen konsistenten Wert für die Variable sehen.

C #

Aus der C # -Referenz zum flüchtigen Schlüsselwort :

Das flüchtige Schlüsselwort gibt an, dass ein Feld im Programm durch etwas wie das Betriebssystem, die Hardware oder einen gleichzeitig ausgeführten Thread geändert werden kann.


Vielen Dank für das Posten. Wie ich in Java verstanden habe, verhält es sich wie das Sperren dieser Variablen in einem Thread-Kontext. In C # kann der Wert der Variablen nicht nur über das Programm geändert werden, sondern auch durch externe Faktoren wie das Betriebssystem. keine Verriegelung impliziert) ... Bitte lassen Sie mich wissen, ob ich diese Unterschiede richtig verstanden habe ...
Mircea

@Mircea in Java ist keine Sperrung erforderlich, es wird lediglich sichergestellt, dass der aktuellste Wert der flüchtigen Variablen verwendet wird.
Rock

Verspricht Java eine Art Speicherbarriere oder verspricht es wie C ++ und C # nur, die Referenz nicht zu optimieren?
Steven Sudit

Die Speicherbarriere ist ein Implementierungsdetail. Was Java tatsächlich verspricht, ist, dass bei allen Lesevorgängen der Wert angezeigt wird, der vom letzten Schreibvorgang geschrieben wurde.
Stephen C

1
@StevenSudit Ja, wenn die Hardware eine Barriere oder ein Laden / Erfassen oder Speichern / Freigeben erfordert, werden diese Anweisungen verwendet. Siehe meine Antwort.
Doug65536

9

In Java wird "flüchtig" verwendet, um der JVM mitzuteilen, dass die Variable von mehreren Threads gleichzeitig verwendet werden kann, sodass bestimmte allgemeine Optimierungen nicht angewendet werden können.

Insbesondere die Situation, in der die beiden Threads, die auf dieselbe Variable zugreifen, auf separaten CPUs auf demselben Computer ausgeführt werden. Es ist sehr üblich, dass CPUs die darin enthaltenen Daten aggressiv zwischenspeichern, da der Speicherzugriff sehr viel langsamer ist als der Cache-Zugriff. Dies bedeutet, dass die Daten, wenn sie in CPU1 aktualisiert werden, sofort alle Caches und den Hauptspeicher durchlaufen müssen, anstatt wenn der Cache sich selbst löscht, damit CPU2 den aktualisierten Wert sehen kann (wiederum durch Ignorieren aller Caches auf dem Weg).


1

Wenn Sie nichtflüchtige Daten lesen, erhält der ausführende Thread möglicherweise nicht immer den aktualisierten Wert. Wenn das Objekt jedoch flüchtig ist, erhält der Thread immer den aktuellsten Wert.


1
Können Sie Ihre Antwort umformulieren?
Anirudha Gupta

Das flüchtige Schlüsselwort gibt Ihnen den aktuellsten Wert anstelle des zwischengespeicherten Werts.
Subhash Saini

0

Volatile löst das Problem der Parallelität. Um diesen Wert synchron zu machen. Dieses Schlüsselwort wird hauptsächlich in einem Threading verwendet. Wenn mehrere Threads dieselbe Variable aktualisieren.


1
Ich denke nicht, dass es das Problem "löst". Es ist ein Werkzeug, das unter bestimmten Umständen hilft. Verlassen Sie sich nicht auf Volatile in Situationen, in denen ein Schloss benötigt wird, wie unter Rennbedingungen.
Scratte
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.