volatile?
-
ist es nötig, dass ich meine Variablen mit volatile deklariere, obwohl ich jeden Zugriff in jedem Thread durch ein boost::recursive_mutex abgesichert habe?
-
Nein, volatile ist nicht notwendig. Das lock/unlock auf die recursive_mutex stellt eine "full memory barrier" dar, damit ist die Sichtbarkeit von Änderungen garantiert. (Natürlich nur wenn alle Zugriffe über ne mutex geschützt sind).
Wenn man "brav" mit mutexen arbeitet (und nicht direkt auf memory mapped Hardware zugreift) braucht man volatile garnicht. Und etwas anderes ist auch nicht zu empfehlen solange man die Plattform mit der man arbeitet nicht sehr gut kennt.
-
Hi,
hmmm - ich dachte, dass man mit volatile den Compiler daran hindern könnte, bestimmte Optimierungen vorzunehmen (z.B. Calls, die in sich keinen Effekt haben, entfernen). Da würde dann auch nicht unbedingt eine Serialisierung helfen.
Außerdem hat es den Vorteil, dass "gemeinsam genutzte Daten" schon in der Deklaration erkennbar sind.
Zumindestens in der Theorie würde ich volatile trotzdem verwenden.Gruß,
Simon2.
-
volatile erklärt eine variable als "extern sichtbar" ("beobachtbar") + "von extern veränderbar".
Da der Standard vorschreibt dass das beobachtbare Verhalten des Programmes dem entsprechen muss was man also Code hinschreibt kann der Compiler natürlich Zugriffe auf "volatile" deklarierte Variablen nicht wegoptimieren oder reordern.
Sogesehen werden also gewisse Optimierungen verhindert.
volatile allerdings zu verwenden um irgendwelche Optimierungen zu "deaktivieren" halte ich für gefährlich bis geisteskrank.volatile zu verwenden um "gemeinsam genutzte Daten erkennbar zu machen" halte ich auch für äusserst unklug, da dann auch innerhalb einer critical section sämtliche Zugriffe auf die gemeinsam genutzten Daten direkt von Speicher kommen/in den Speicher gehen - nicht sehr gut was die Performance angeht.
Ich würde jedem empfehlen *niemals* volatile zu schreiben und das Keyword am besten gleich ganz zu vergessen. Wenn jmd. doch glaubt es sei nötig macht er etwas falsch.
-
Ich würde jedem empfehlen *niemals* volatile zu schreiben und das Keyword am besten gleich ganz zu vergessen. Wenn jmd. doch glaubt es sei nötig macht er etwas falsch.
Dazu würde mich die Meinung von 'camper' interessieren.
-
hustbaer schrieb:
volatile erklärt eine variable als "extern sichtbar" ("beobachtbar") + "von extern veränderbar".
Heist das also, dass man auf die Speicheradresse einer als volatile deklarierten Variable mit einem externen Programm zugreifen kann, ohne dass es eine Access Violation gibt?
-
hustbaer schrieb:
...
Ich würde jedem empfehlen *niemals* volatile zu schreiben und das Keyword am besten gleich ganz zu vergessen. Wenn jmd. doch glaubt es sei nötig macht er etwas falsch.
Nicht zwingenderweise. Vielleicht moechtest du, dass eine Variable nicht
wegoptimiert wird, weil sie u. U. von irgendwo manipuliert werden soll. In
einem solchen Falle kannst du mit volatile arbeiten.gruss
v R
-
virtuell Realisticer schrieb:
hustbaer schrieb:
...
Ich würde jedem empfehlen *niemals* volatile zu schreiben und das Keyword am besten gleich ganz zu vergessen. Wenn jmd. doch glaubt es sei nötig macht er etwas falsch.
Nicht zwingenderweise. Vielleicht moechtest du, dass eine Variable nicht
wegoptimiert wird, weil sie u. U. von irgendwo manipuliert werden soll. In
einem solchen Falle kannst du mit volatile arbeiten.gruss
v Rich schließe mich v R an. Spätestens wenn du für irgendwelche 64-Bit Monsterkontroller programmierst, welche dir 128 allgemeine Register anbieten, kann IMO folgender Code ohne volatile kritisch werden.
int i=0; recursive_mutex m; void MeinThread() { { recursive_mutex::scoped_lock Lock(m); ++i; } thread::yield(); { recursive_mutex::scoped_lock Lock(m); ++i; } thread::yield(); { recursive_mutex::scoped_lock Lock(m); ++i; } //... }Der Compiler darf, wenn i nicht volatile ist, den Wert von i in einem Register zwischenspeichern, um diesen dann später im zweiten/dritten/... Block zu vergrößern und zurück nach i zu schreiben. Falls dazwischen ein anderer Thread i verändert wird trotz der scoped_lock's das Ergebnis falsch sein.
Der Fall an sich ist natürlich sehr abstrakt aber theoretisch natürlich möglich und letzendlich ist soetwas eine schöne Fehlerquelle, welche man nicht so leicht findet.
[EDIT]Ich vermute, dass der Compiler, wenn i ein int* wäre (und dann (*pi) inkremiert würde), das Ergebnis wirklich in einem Register speichert, da die Kosten dann (in einer Single-Thread-Umgebung) weitaus geringer wären.[/EDIT]
-
@mikey: nein, das heisst nicht dass man von extern zugreifen kann, es heisst nur der Compiler muss die Variable so behandeln als könnte man. Das ist ein riesen Unterschied.
Trundle0x7e schrieb:
virtuell Realisticer schrieb:
hustbaer schrieb:
...
Ich würde jedem empfehlen *niemals* volatile zu schreiben und das Keyword am besten gleich ganz zu vergessen. Wenn jmd. doch glaubt es sei nötig macht er etwas falsch.
Nicht zwingenderweise. Vielleicht moechtest du, dass eine Variable nicht
wegoptimiert wird, weil sie u. U. von irgendwo manipuliert werden soll. In
einem solchen Falle kannst du mit volatile arbeiten.gruss
v Rich schließe mich v R an. Spätestens wenn du für irgendwelche 64-Bit Monsterkontroller programmierst, welche dir 128 allgemeine Register anbieten, kann IMO folgender Code ohne volatile kritisch werden.
int i=0; recursive_mutex m; void MeinThread() { { recursive_mutex::scoped_lock Lock(m); ++i; } thread::yield(); { recursive_mutex::scoped_lock Lock(m); ++i; } thread::yield(); { recursive_mutex::scoped_lock Lock(m); ++i; } //... }Der Compiler darf, wenn i nicht volatile ist, den Wert von i in einem Register zwischenspeichern, um diesen dann später im zweiten/dritten/... Block zu vergrößern und zurück nach i zu schreiben. Falls dazwischen ein anderer Thread i verändert wird trotz der scoped_lock's das Ergebnis falsch sein.
Der Fall an sich ist natürlich sehr abstrakt aber theoretisch natürlich möglich und letzendlich ist soetwas eine schöne Fehlerquelle, welche man nicht so leicht findet.
[EDIT]Ich vermute, dass der Compiler, wenn i ein int* wäre (und dann (*pi) inkremiert würde), das Ergebnis wirklich in einem Register speichert, da die Kosten dann (in einer Single-Thread-Umgebung) weitaus geringer wären.[/EDIT]
Nein, der Code funktioniert, kein Problem.
Der Compiler kann i sowieso nicht in einem Register halten, da i eine globale Variable ist, und die Funktion die in recursive_mutex::scoped_lock:: (~)scoped_lock aufgerufen wird ihm unbekannt ist. Daher muss er davon ausgehen dass diese Funktion (z.B. pthread_mutex_lock, EnterCriticalSection, WaitForSingleObject, ...) auf i lesend und/oder schreiben zugreift. Daher muss er den Wert vor dem lock zurückschreiben und nach dem lock neu lesen.
Die noch nötigen "fence" instructions für die CPU müssen dann in pthread_mutex_lock, EnterCriticalSection etc. enthalten sein (und sind es auch).Das ist die "halbrichtige" dafür eingängige Erklärung, und der Grund warum es auch mit Compilern funktioniert die von POSIX oder dem pthreads Speichermodell keinen Schimmer haben.
Und mit Compilern die das pthreads oder win32 Speichermodell kennen und unterstützen ist es erst recht kein Problem, da diese die entsprechenden Funktionen zu kennen die eine "barrier" darstellen, und diese auch respektieren. Die "richtige" Erklärung ist dass die win32 Plattform bzw. das win32 Speichermodell (oder eben pthreads/POSIX Speichermodell) eben genau garantieren dass es funktioniert. Funktioniert es nicht ist der Compiler einfach kaputt bzw. freundlich ausgedrückt "nicht konform".
Also nochmal: wenn man Locks im Rahmen des verwendeten Speichermodells verwendet braucht man *niemals* volatile.
> Vielleicht moechtest du, dass eine Variable nicht
wegoptimiert wird, weil sie u. U. von irgendwo manipuliert werden soll. <<Hem. Wenn ich die Variable von irgendwo manipulieren will, dann muss die Adresse "irgendwo" bekannt sein. Da der Compiler dann weiss dass die Adresse der Variable irgendwo bekannt ist muss er eben z.B. nach jeder Funktion von der er nicht weiss was sie genau tun könnte den Wert neu einlesen.
Verwendet man brav Locks wo sie hingehören fallen alle lock/unlock Funktionen in diese Kategorie. Verwendet man keine Locks hat man die Variable auch nicht aus einem anderen Thread anzugreifen, und es gibt wieder kein Problem.> Ich vermute, dass der Compiler, wenn i ein int* wäre (und dann (*pi) inkremiert würde), das Ergebnis wirklich in einem Register speichert, da die Kosten dann (in einer Single-Thread-Umgebung) weitaus geringer wären
Der Compiler würde (wenn man die scoped locks & das yield wegnimmt) die 3 "i++" einfach durch ein "i += 3" ersetzen, und das vermute ich nicht, da bin ich mir ganz sicher. Verhindert wird dies allerdings wie bereits gesagt durch die Aufrufe dazwischen.
-
hustbaer schrieb:
Der Compiler kann i sowieso nicht in einem Register halten, da i eine globale Variable ist, und die Funktion die in recursive_mutex::scoped_lock:: (~)scoped_lock aufgerufen wird ihm unbekannt ist. Daher muss er davon ausgehen dass diese Funktion (z.B. pthread_mutex_lock, EnterCriticalSection, WaitForSingleObject, ...) auf i lesend und/oder schreiben zugreift. Daher muss er den Wert vor dem lock zurückschreiben und nach dem lock neu lesen.
Die noch nötigen "fence" instructions für die CPU müssen dann in pthread_mutex_lock, EnterCriticalSection etc. enthalten sein (und sind es auch).Richtig, das ist mir entfallen.
hustbaer schrieb:
Der Compiler würde (wenn man die scoped locks & das yield wegnimmt) die 3 "i++" einfach durch ein "i += 3" ersetzen, und das vermute ich nicht, da bin ich mir ganz sicher. Verhindert wird dies allerdings wie bereits gesagt durch die Aufrufe dazwischen.
Das ist klar. Ich ging davon aus, dass die mutex'e natürlich noch da sind.
Naja, hast ja 'eh recht. Wieder was gelernt...