Soweit ich weiß, optimiert der Compiler niemals eine Variable, die als volatile
deklariert ist. Allerdings habe ich ein Array wie folgt deklariert.
Und verschiedene Threads lesen und schreiben darauf. Ein Element des Arrays wird nur von einem der Threads modifiziert und von einem anderen Thread gelesen. In bestimmten Situationen habe ich jedoch festgestellt, dass selbst wenn ich ein Element aus einem Thread ändere, der Thread, der es liest, die Änderung nicht bemerkt. Es liest den gleichen alten Wert weiter, als hätte der Compiler es irgendwo zwischengespeichert. Aber der Compiler sollte grundsätzlich keine flüchtige Variable zwischenspeichern, richtig? Also, wie kommt es dazu?
HINWEIS: Ich verwende volatile
nicht für die Thread-Synchronisation, also bitte hört auf, mir Antworten wie eine Sperre oder eine atomare Variable zu geben. Ich kenne den Unterschied zwischen flüchtigen, atomaren Variablen und Mutexen. Beachten Sie auch, dass die Architektur x86 mit proaktiver Cache-Kohärenz ist. Außerdem lese ich die Variable lange genug, nachdem sie angeblich vom anderen Thread geändert wurde. Auch nach einer langen Zeit kann der Lese-Thread den geänderten Wert nicht sehen.
Aber der Compiler sollte im Prinzip keine flüchtige Variable zwischenspeichern, richtig?
Nein, der Compiler muss die Adresse der Variablen grundsätzlich lesen / schreiben, wenn Sie die Variable lesen / schreiben.
[Edit: Zumindest muss dies bis zu dem Punkt geschehen, an dem die Implementierung glaubt, dass der Wert an dieser Adresse "beobachtbar" ist. Wie Dietmar in seiner Antwort ausführt, könnte eine Implementierung erklären, dass normales Gedächtnis "nicht beobachtet werden kann". Dies wäre für Leute, die Debugger verwenden, eine Überraschung, mprotect
, oder andere Sachen, die außerhalb des Geltungsbereiches des Standards liegen, aber es könnte im Prinzip übereinstimmen.]
In C ++ 03, das überhaupt keine Threads berücksichtigt, muss die Implementierung definieren, was "auf die Adresse zugreifen" bedeutet, wenn sie in einem Thread ausgeführt wird. Details wie diese werden als "Speichermodell" bezeichnet. Pthreads zum Beispiel ermöglicht die Thread-Zwischenspeicherung des gesamten Speichers, einschließlich der flüchtigen Variablen. IIRC, MSVC bietet eine Garantie, dass volatile Variablen geeigneter Größe atomar sind, und es vermeidet Caching (eher wird es bis zu einem einzigen kohärenten Cache für alle Kerne fluten). Der Grund, warum es diese Garantie bietet, ist, dass es bei Intel billig ist - Windows interessiert sich nur für Intel-basierte Architekturen, während Posix sich mit exotischeren Dingen beschäftigt.
C ++ 11 definiert ein Speichermodell für Threading und sagt, dass dies ein Datenrennen ist (dh dass volatile
nicht sicherstellt, dass ein Lesevorgang in einem Thread relativ zu a. sequenziert wird schreibe in einen anderen Thread). Zwei Zugriffe können in einer bestimmten Reihenfolge sequenziert werden, in nicht spezifizierter Reihenfolge sequenziert werden (der Standard könnte sagen "unbestimmte Reihenfolge", ich kann mich nicht erinnern) oder überhaupt nicht sequenziert. Nicht sequenziert überhaupt ist schlecht - wenn einer von zwei nicht-sequenzierten Zugriffen ein Schreiben ist, dann ist das Verhalten undefiniert.
Der Schlüssel hier ist das implizierte "und dann" in "Ich modifiziere ein Element aus einem Thread und dann bemerkt der Thread, der es liest, die Änderung nicht". Sie gehen davon aus, dass die Vorgänge sequenziert sind, aber nicht. Soweit es den Lese-Thread betrifft, ist der Schreibvorgang in dem anderen Thread nicht unbedingt erforderlich, es sei denn, Sie verwenden eine Art von Synchronisation. Und eigentlich ist es schlimmer als das - Sie könnten von dem, was ich gerade geschrieben habe, denken, dass nur die Reihenfolge der Operationen nicht spezifiziert ist, aber das Verhalten eines Programms mit einem Datenrennen ist undefiniert.
C
Was flüchtig ist:
Was flüchtig ist nicht:
Was flüchtig ist oder nicht:
Mit volatile
können Sie nur festlegen, dass eine Variable erneut gelesen wird, wenn Sie ihren Wert verwenden. Es garantiert nicht, dass die verschiedenen Werte / Repräsentationen, die auf verschiedenen Ebenen Ihrer Architektur vorhanden sind, konsistent sind.
Um solche Garantien zu haben, brauchen Sie die neuen Dienstprogramme von C11 und C ++ 1 bezüglich atomarer Zugriffe und Speicherbarrieren. Viele Compiler implementieren diese bereits in Bezug auf die Erweiterung. Zum Beispiel haben die gcc-Familie (clang, icc, etc) eingebaute mit dem Präfix __sync
, um diese zu implementieren.
Soweit ich weiß, optimiert der Compiler niemals eine Variable, die als flüchtig deklariert ist.
Ihre Prämisse ist falsch. volatile
ist ein Hinweis auf den Compiler und garantiert eigentlich nichts. Compiler können einige Optimierungen für volatile
Variablen verhindern, aber das war's.
volatile
ist keine Sperre, versuchen Sie nicht, sie als solche zu verwenden.
7) [Hinweis: volatile ist ein Hinweis auf die zu vermeidende Implementierung aggressive Optimierung mit dem Objekt, weil der Wert der Das Objekt kann durch eine nicht implementierbare Einrichtung geändert werden. Eine detaillierte Semantik finden Sie in 1.9. Im Allgemeinen ist die Semantik von flüchtigen sollen in C ++ gleich sein wie in C.-Endnote]
Das Schlüsselwort volatile
hat nichts mit der Parallelität in C ++ überhaupt zu tun! Es wird verwendet, um zu verhindern, dass der Compiler den vorherigen Wert verwendet, d. H. Der Compiler erzeugt bei jedem Zugriff auf den Code einen Code, der auf einen volatile
-Wert zugreift. Der Hauptzweck sind Dinge wie Memory-Mapped I / O. Die Verwendung von volatile
hat jedoch no Auswirkungen auf das, was die CPU beim Lesen von normalem Speicher tut: Wenn die CPU keinen Grund zu der Annahme hat, dass sich der Wert im Speicher geändert hat, z. B. weil keine Synchronisationsanweisung vorhanden ist Es kann nur den Wert aus seinem Cache verwenden. Um zwischen den Threads zu kommunizieren, benötigen Sie eine Synchronisation, z. B. std::atomic<T>
, lock a std::mutex
, etc.
Volatile
Schlüsselwort garantiert nur, dass der Compiler kein Register für diese Variable verwendet. Somit wird jeder Zugriff auf diese Variable gehen und den Speicherort lesen. Jetzt nehme ich an, dass Sie Cache-Kohärenz zwischen den mehreren Prozessoren in Ihrer Architektur haben. Wenn also ein Prozessor schreibt und andere es liest, sollte es unter normalen Bedingungen sichtbar sein. Sie sollten jedoch die Eckfälle berücksichtigen. Angenommen, die Variable befindet sich in der Pipeline eines Prozessorkerns, und ein anderer Prozessor versucht, sie zu lesen, vorausgesetzt, dass sie geschrieben wurde, dann liegt ein Problem vor. Im Wesentlichen sollten die geteilten Variablen entweder durch Sperren geschützt werden oder sollten durch den Barrieremechanismus korrekt geschützt werden.
Volatile wirkt sich nur auf die Variable aus, vor der es liegt. Hier in Ihrem Beispiel ein Zeiger. Ihr Code: volatile long array [8], der Zeiger auf das erste Element des Arrays ist flüchtig, nicht der Inhalt. (Gleiches gilt für Objekte jeglicher Art)
Sie könnten es wie in anpassen Wie deklariere ich Ein Array, das mit malloc erstellt wurde, um in C ++ flüchtig zu sein
In bestimmten Situationen habe ich jedoch festgestellt, dass selbst wenn ich ein modifiziere Element aus einem Thread, der Thread liest es nicht bemerkt Veränderung. Es liest den gleichen alten Wert, als hätte der Compiler irgendwo zwischengespeichert.
Dies liegt nicht daran, dass der Compiler sie irgendwo zwischengespeichert hat, sondern weil der Lese-Thread aus dem Cache seines CPU-Kerns liest, der sich möglicherweise von dem des schreibenden Threads unterscheidet. Um die Weitergabe von Wertänderungen über CPU-Kerne sicherzustellen, müssen Sie geeignete Speicherzäune verwenden, und in C ++ können Sie dafür weder flüchtig noch flüchtig sein.
Die Semantik von volatile
ist implementierungsdefiniert. Wenn ein Compiler wüsste, dass Interrupts während der Ausführung bestimmter Codeabschnitte deaktiviert wären, und wüsste, dass es auf der Zielplattform andere Mittel als Interrupt-Handler geben würde, über die Vorgänge auf einem bestimmten Speicher beobachtbar wären, könnte er -cache volatile
-qualifizierte Variablen in einem solchen Speicher genau so, wie sie gewöhnliche Variablen zwischenspeichern könnten, vorausgesetzt, sie dokumentierten ein solches Verhalten.
Beachten Sie, dass einige Aspekte des Verhaltens als "beobachtbar" gewertet werden und in gewissem Maße durch die Implementierung definiert werden können. Wenn eine Implementierung dokumentiert, dass sie nicht für die Verwendung auf Hardware gedacht ist, die Haupt-RAM-Zugriffe verwendet, um erforderliche extern sichtbare Aktionen auszulösen, dann wären Zugriffe auf das Haupt-RAM bei dieser Implementierung nicht "beobachtbar". Die Implementierung wäre mit Hardware kompatibel, die solche Zugriffe physisch beobachten könnte, wenn es nicht darauf ankommt, ob solche Zugriffe tatsächlich gesehen werden. Waren solche Zugriffe jedoch erforderlich, wie dies der Fall wäre, wenn die Zugriffe als "beobachtbar" galten, würde der Compiler keine Kompatibilität beanspruchen und somit nichts versprechen.
C ++ - Zugriffe durch flüchtige L-Werte und C-Zugriffe auf flüchtige Objekte sind "abstrakt" "beobachtbar" - obwohl in der Praxis Das C-Verhalten entspricht dem C ++ - Standard und nicht dem C-Standard. Informell teilt die Deklaration volatile
jedem -Thread mit, dass sich der Wert irgendwie ändern könnte, unabhängig vom Text in jedem -Thread. Unter den Standards mit Threads gibt es keine Vorstellung von einem Schreiben durch einen anderen Thread, der eine Änderung in einem Objekt verursacht, flüchtig oder nicht, geteilt oder nicht, außer für eine gemeinsam genutzte Variable durch den Synchronisierungsfunktionsaufruf zu Beginn eines synchronisierten kritischen Objekts Region. volatile
ist irrelevant , um gemeinsam genutzte Objekte einzufügen.
Wenn Ihr Code den Thread, über den Sie sprechen, nicht richtig synchronisiert, hat Ihr einziger Thread, der liest, was ein anderer Thread geschrieben hat, ein undefiniertes Verhalten. Der Compiler kann also beliebigen Code generieren. Wenn Ihr Code ordnungsgemäß synchronisiert ist, werden Schreibvorgänge durch andere Threads nur bei Threadsynchronisierungsaufrufen ausgeführt. Sie brauchen dafür nicht volatile
.
PS
Die Standards sagen "Was einen Zugang zu einem Objekt ausmacht, das Ist der Typ flüchtig qualifiziert, ist die Implementierung definiert. "Sie können also nicht einfach davon ausgehen, dass für jede Dereferenzierung eines flüchtigen L-Werts oder Schreibzugriffs für jede Zuweisung ein Lesezugriff besteht.
Außerdem ist die Implementierung (definiert als "abstrakt") "beobachtbar" volatile
Zugriffe "tatsächlich" manifestiert. Ein Compiler generiert daher möglicherweise keinen Code für Hardwarezugriffe, die den definierten abstrakten Zugriffen entsprechen. ZB können nur Objekte mit statischer Speicherdauer und externer Verknüpfung, die mit einem bestimmten Flag zum Verknüpfen in spezielle Hardware-Positionen kompiliert wurden, von außerhalb des Programmtextes verändert werden, so dass% ce_de% anderer Objekte ignoriert wird.
Tags und Links c c++ multithreading x86 compiler-construction