flüchtig (Computerprogrammierung) - volatile (computer programming)


In Computer - Programmierung , vor allem in der C , C ++ , C # und Java Programmiersprachen , das volatile Schlüsselwort gibt an, dass ein Wert zwischen verschiedenen Zugriffen ändern kann, auch wenn es nicht geändert zu werden scheint. Dieses Schlüsselwort verhindert, dass ein optimierender Compiler nachfolgende Lese- oder Schreibvorgänge wegoptimiert und somit einen veralteten Wert falsch wiederverwendet oder Schreibvorgänge auslässt. Flüchtige Werte treten hauptsächlich beim Hardwarezugriff ( Memory-mapped I/O ) auf, bei dem das Lesen aus oder das Schreiben in den Speicher verwendet wird, um mit Peripheriegeräten zu kommunizieren , und beim Threading , bei dem ein anderer Thread einen Wert geändert haben kann.

Obwohl es sich um ein häufiges Schlüsselwort handelt, unterscheidet sich das Verhalten von volatileerheblich zwischen den Programmiersprachen und wird leicht missverstanden. In C und C++ ist es ein Typqualifizierer wie const, und ist eine Eigenschaft des Typs . Weiterhin ist in C und C ++ funktioniert es nicht funktioniert in den meisten Threading Szenarien, und dass die Verwendung abgeraten. In Java und C# ist es eine Eigenschaft einer Variablen und gibt an, dass das Objekt, an das die Variable gebunden ist, mutieren kann und speziell für das Threading gedacht ist. In der Programmiersprache D gibt es ein separates Schlüsselwort sharedfür die Threading-Verwendung, aber es volatileexistiert kein Schlüsselwort.

In C und C++

In C und folglich in C++ sollte das volatileSchlüsselwort

  • erlauben den Zugriff auf speicherabgebildete E/A- Geräte
  • erlauben die Verwendung von Variablen zwischen setjmpundlongjmp
  • erlauben die Verwendung von sig_atomic_tVariablen in Signalhandlern.

Obwohl sowohl von C als auch von C++ beabsichtigt, können die C-Standards nicht ausdrücken, dass sich die volatileSemantik auf den lvalue bezieht, nicht auf das referenzierte Objekt. Die entsprechende Mängelanzeige DR 476 (zu C11) wird noch mit C17 geprüft.

Operationen auf volatileVariablen sind weder atomar noch stellen sie eine richtige Vorher-Beziehung für das Threading her. Dies ist in den relevanten Standards (C, C++, POSIX , WIN32) spezifiziert, und volatile Variablen sind in den allermeisten aktuellen Implementierungen nicht threadsicher. Daher volatilewird von vielen C/C++-Gruppen von der Verwendung des Schlüsselworts als tragbarer Synchronisationsmechanismus abgeraten.

Beispiel für speicherabgebildete E/A in C

In diesem Beispiel setzt der Code den in gespeicherten Wert fooauf 0. Es beginnt dann, diesen Wert wiederholt abzufragen , bis er sich in ändert 255:

static int foo;

void bar(void) {
    foo = 0;

    while (foo != 255)
         ;
}

Ein optimierender Compiler wird feststellen, dass kein anderer Code den in gespeicherten Wert möglicherweise ändern kann foo, und geht davon aus, dass er 0jederzeit gleich bleibt . Der Compiler ersetzt daher den Funktionsrumpf durch eine Endlosschleife ähnlich dieser:

void bar_optimized(void) {
    foo = 0;

    while (true)
         ;
}

Jedoch fookönnte eine Stelle dar , die durch andere Elemente des Computersystems zu jeder Zeit, wie beispielsweise ein verändert werden können Hardwareregister einer Vorrichtung mit der CPU . Der obige Code würde eine solche Änderung niemals erkennen; ohne das volatileSchlüsselwort geht der Compiler davon aus, dass das aktuelle Programm der einzige Teil des Systems ist, der den Wert ändern könnte (was bei weitem die häufigste Situation ist).

Um zu verhindern, dass der Compiler Code wie oben beschrieben optimiert, wird das volatileSchlüsselwort verwendet:

static volatile int foo;

void bar (void) {
    foo = 0;

    while (foo != 255)
        ;
}

Bei dieser Modifikation wird die Schleifenbedingung nicht wegoptimiert und das System erkennt die Änderung, wenn sie auftritt.

Im Allgemeinen gibt es auf Plattformen verfügbare Speicherbarriereoperationen (die in C++11 verfügbar gemacht werden), die anstelle von flüchtigen bevorzugt werden sollten, da sie dem Compiler eine bessere Optimierung ermöglichen und vor allem ein korrektes Verhalten in Multithread-Szenarien garantieren. Weder die C-Spezifikation (vor C11) noch die C++-Spezifikation (vor C++11) spezifizieren ein Multithread-Speichermodell, sodass sich volatile möglicherweise nicht deterministisch über Betriebssysteme/Compiler/CPUs hinweg verhalten.

Optimierungsvergleich in C

Die folgenden C-Programme und zugehörigen Assemblys veranschaulichen, wie sich das volatileSchlüsselwort auf die Ausgabe des Compilers auswirkt. Der Compiler war in diesem Fall GCC .

Beim Betrachten des Assemblercodes ist deutlich zu erkennen, dass der mit volatileObjekten generierte Code ausführlicher ist, was ihn länger macht, damit die Natur von volatileObjekten erfüllt werden kann. Das volatileSchlüsselwort verhindert, dass der Compiler eine Optimierung des Codes mit flüchtigen Objekten durchführt, wodurch sichergestellt wird, dass jede flüchtige Variablenzuweisung und jeder Lesevorgang einen entsprechenden Speicherzugriff hat. Ohne das volatileSchlüsselwort weiß der Compiler, dass eine Variable nicht bei jeder Verwendung neu aus dem Speicher gelesen werden muss, da keine Schreibvorgänge von anderen Threads oder Prozessen in ihre Speicherposition erfolgen sollten.

C++11

Laut C++11 ISO-Standard ist das Schlüsselwort volatile nur für den Hardwarezugriff gedacht; Verwenden Sie es nicht für die Kommunikation zwischen Threads. Für die Kommunikation zwischen Threads stellt die Standardbibliothek std::atomic<T>Vorlagen bereit .

Auf Java

Die Programmiersprache Java hat auch das volatileSchlüsselwort, aber es wird für einen etwas anderen Zweck verwendet. Bei Anwendung auf ein Feld bietet der Java-Qualifizierer volatiledie folgenden Garantien:

  • In allen Java-Versionen gibt es eine globale Reihenfolge für Lese- und Schreibvorgänge aller flüchtigen Variablen (diese globale Reihenfolge für flüchtige Variablen ist eine Teilreihenfolge gegenüber der größeren Synchronisationsreihenfolge (die eine Gesamtreihenfolge über alle Synchronisationsaktionen ist )). Dies impliziert, dass jeder Thread, der auf ein flüchtiges Feld zugreift, seinen aktuellen Wert liest, bevor er fortfährt, anstatt (möglicherweise) einen zwischengespeicherten Wert zu verwenden. (Es gibt jedoch keine Garantie für die relative Reihenfolge flüchtiger Lese- und Schreibvorgänge mit regulären Lese- und Schreibvorgängen, was bedeutet, dass es sich im Allgemeinen nicht um ein nützliches Threading-Konstrukt handelt.)
  • In Java 5 oder höher stellen flüchtige Lese- und Schreibvorgänge eine Ereignis-before-Beziehung her , ähnlich wie das Erfassen und Freigeben eines Mutex.

Die Verwendung ist volatilemöglicherweise schneller als eine Sperre , funktioniert jedoch in einigen Situationen vor Java 5 nicht. Der Bereich der Situationen, in denen volatile wirksam ist, wurde in Java 5 erweitert; insbesondere funktioniert die doppelt geprüfte Verriegelung jetzt korrekt.

In C#

In C # , volatilestellt sicher , dass das Code - Feld ist nicht Gegenstand einiger gewinde unsicherer Optimierungen , die durch den Compiler durchgeführt werden, die CLR, oder durch die Hardware zugreifen können. Wenn ein Feld markiert ist volatile, wird der Compiler angewiesen, eine "Speicherbarriere" oder einen "Zaun" darum herum zu erzeugen, die eine Neuordnung von Befehlen oder eine an das Feld gebundene Zwischenspeicherung verhindert. Beim Lesen eines volatileFelds generiert der Compiler einen Acquire-Fence , der verhindert, dass andere Lese- und Schreibvorgänge in das Feld, einschließlich solcher in anderen Threads, vor den Fence verschoben werden . Beim Schreiben in ein volatileFeld generiert der Compiler einen Release-Fence ; Dieser Zaun verhindert, dass andere Lese- und Schreibvorgänge in das Feld nach dem Zaun verschoben werden .

Nur die folgenden Typen können markiert werden volatile: alle Verweistypen, Single, Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Char, und alle Aufzählungstypen mit dem zugrunde liegenden Typ Byte, SByte, Int16, UInt16, Int32, oder UInt32. (Dies schließt Wert structs , sowie die primitiven Typen Double, Int64, UInt64und Decimal.)

Die Verwendung des volatileSchlüsselworts unterstützt keine Felder, die als Referenz übergeben werden, oder erfasste lokale Variablen ; in diesen Fällen Thread.VolatileReadund Thread.VolatileWritemuss stattdessen verwendet werden.

Tatsächlich deaktivieren diese Methoden einige Optimierungen, die normalerweise vom C#-Compiler, dem JIT-Compiler oder der CPU selbst durchgeführt werden. Die Garantien von Thread.VolatileReadund Thread.VolatileWritesind eine Obermenge der Garantien des volatileSchlüsselworts: statt einen "Half-Fence" zu erzeugen (dh ein Acquire-Fence verhindert nur die Neuordnung von Anweisungen und das Caching davor), VolatileReadund VolatileWriteerzeugen Sie einen "Full-Fence", der verhindern die Neuordnung von Anweisungen und das Zwischenspeichern dieses Felds in beide Richtungen. Diese Methoden funktionieren wie folgt:

  • Die Thread.VolatileWriteMethode erzwingt, dass der Wert im Feld zum Zeitpunkt des Aufrufs geschrieben wird. Außerdem müssen alle früheren Lade- und Speichervorgänge in Programmreihenfolge vor dem Aufruf an VolatileWriteund alle späteren Lade- und Speichervorgänge in Programmreihenfolge nach dem Aufruf erfolgen.
  • Die Thread.VolatileReadMethode erzwingt, dass der Wert im Feld zum Zeitpunkt des Aufrufs gelesen wird. Außerdem müssen alle früheren Lade- und Speichervorgänge in Programmreihenfolge vor dem Aufruf an VolatileReadund alle späteren Lade- und Speichervorgänge in Programmreihenfolge nach dem Aufruf erfolgen.

Die Thread.VolatileReadand- Thread.VolatileWriteMethoden generieren einen vollständigen Zaun, indem sie die Thread.MemoryBarrierMethode aufrufen , die eine Speicherbarriere erstellt, die in beide Richtungen funktioniert. Zusätzlich zu den oben angegebenen Motivationen für die Verwendung eines vollständigen Zauns ist ein potenzielles Problem mit dem volatileSchlüsselwort, das durch die Verwendung eines vollständigen Zauns gelöst wird, der von erzeugt Thread.MemoryBarrierwird, wie folgt: Aufgrund der asymmetrischen Natur von Halbzäunen wird ein volatileFeld mit einem Schreibbefehl gefolgt von bei einem Lesebefehl kann die Ausführungsreihenfolge immer noch vom Compiler getauscht werden. Da Vollzäune symmetrisch sind, ist dies bei der Verwendung kein Problem Thread.MemoryBarrier.

In Fortran

VOLATILEist Teil des Fortran 2003- Standards, obwohl eine frühere Version ihn als Erweiterung unterstützte. Das Erstellen aller Variablen volatilein einer Funktion ist auch nützlich, um Aliasing- bezogene Fehler zu finden.

integer, volatile :: i ! When not defined volatile the following two lines of code are identical
write(*,*) i**2  ! Loads the variable i once from memory and multiplies that value times itself
write(*,*) i*i   ! Loads the variable i twice from memory and multiplies those values

Durch immer "Drilldown" in den Speicher eines VOLATILE wird der Fortran-Compiler daran gehindert, Lese- oder Schreibvorgänge auf flüchtige Elemente neu zu ordnen. Dadurch werden Aktionen in diesem Thread für andere Threads sichtbar und umgekehrt.

Der Einsatz von VOLATILE reduziert die Optimierung und kann sie sogar verhindern.

Verweise

Externe Links