volatile notwendig?
-
Na schön. Ich habe das Beispiel etwas abgewandelt.
Würde es jetzt Sinn machen, das g_storage-Objekt volatile zu machen? Es könnte ja sein, dass es, im Rahmen der Funktion NewString(), im Cache zwischengespeichert wird und die Funktion DestroyString() währenddessen auf das g_storage-Objekt auf dem Stack zugreift. Oder nicht? Es würde mich generell interessieren, wann eine Variable, welche für den schnelleren Zugriff ins Cache geholt wurde, wieder ins RAM verschoben wird. Ist das immer am Ende der Funktion (wenn diese nicht inline ist)?
#include <iostream> #include <string> #include <set> #include <boost/thread/mutex.hpp> std::set<std::string*> g_storage; boost::mutex g_mutex; std::string* NewString() { std::string* s = new std::string; { boost::mutex::scoped_lock lock(g_mutex); g_storage.insert(s); } // Mach hier noch irgend was... return s; } void DestroyString(std::string* s) { boost::mutex::scoped_lock lock(g_mutex); std::set<std::string*>::iterator i = g_storage.find(s); if(i != g_storage.end()) { delete s; g_storage.erase(i); } }
-
Du schützt mit deiner Mutex bloss g_storage, nicht die darin gespeichertern Strings.
Da du einen mutable String zurückgibst ist das etwas fragwürdig.Ich würde dir empfehlen dich mal etwas in das Thema Multithreading einzulesen.
Nur so ein Tip am Rande: wenn du mit Mutexen arbeitest brauchst du kein volatile, niemals, nirgends. Es sei denn du willst irgendwas ganz schlau optimieren, und davon lass mal lieber die Finger. Wenn du solche Fragen stellst bist du nämlich noch weit davon weg das korrekt hinzubekommen.
-
volatile kann durchaus notwendig sein.
Beispielcode (es ist ein Beispiel, nicht zum Nachmachen gedacht :)) :
void f( int& i) { i = 1; while( i == 1) { do_something(); } } void g( int & i) { do_something_else(); i = 0; } int main() { int x = 1; start_thread( f, x); start_thread( g, x); }
Synchronisation, schlechter Stil, syntaktisch unkorrekt - bitte alles mal vernachlässigen.
In f wird ein optimierender Compiler möglicherweise eine Endlosschleife einbauen, wenn er das i=1 und das nachfolgende i == 1 zusammenfasst.
Das ist nur ein akademisches Beispiel (also bitte keine Flames wie man das besser machen kann :D), aber hier kann volatile entscheidend sein.
-
Also, bisher bin ich wohl fälschlicherweise davon ausgegangen, dass der Compiler zur Optimierung was-auch-immer in das Prozessorcache verlegen kann (wegen schnelleren Zugriffs), aber in Wirklich keit entscheidet das die CPU, nicht?
Bleiben als die Register. Folglich sollte man alle Typen, die in ein Register passen, und auf die von mehreren Threads aus zugegriffen wird, als volatile spezifizieren. Richtig?
Ein Mutex kann ja nicht kontrollieren wie lange ein Wert im register bleibt, und wann er zurück ins RAM geschrieben wird. Nicht?
-
Nein, nicht richtig. Wenn die Daten von einem anderen Prozessor bereits gecacht wurden, wird dessen Cacheline invalidiert und er muss sich die Daten neu aus dem Arbeitsspeicher laden. Möglicherweise machen moderne Prozessoren das auch intelligenter.
volatile sagt dem Compiler, dass ein Wert sich quasi "spontan" ändern kann und er die geringst-möglichen Annahmen über die Konstanz dieses Wertes macht. Das ist afaik nicht weiter standardisiert, daher hat das Thema relativ wenig solide Substanz zum Diskutieren.
-
7H3 N4C3R schrieb:
volatile kann durchaus notwendig sein.
...
Das ist nur ein akademisches Beispiel (also bitte keine Flames wie man das besser machen kann :D), aber hier kann volatile entscheidend sein.volatile hilft hier nicht. Im Prinzip. Natürlich kann sich ein bestimmter Compiler bei Anwesenheit von volatile so verhalten, wie du es erwartest - aber das ist nicht garantiert.
Das hier zugrunde liegende Problem ist Sichtbarkeit von Zugriffen aus anderen Threads und zeitliche Ordnung. volatile hilft nur bei Letzterem und auch dort nur bzgl. des gleichen Threads - was sowieso unproblematisch ist.volatile ist in C++ unterspezifiziert, aber das betrifft im Wesentlichen nur das Problem, was passiert, wenn man volatile auf Klassen anwendet.
Zwei Gedankenspiele können helfen.
1. Betrachte 2 Threads, einer schreibt permanent abwechselnd einen von zwei Werten an eine Speicherzelle; der andere liest permanent den Wert (wir nehmen an, dass beide Zugriffe "atomar" erfolgen, es wird also immer nur ein Wert gelesen, der so auch mal geschrieben wurde). Keine besonderen Synchronisationsmechanismen sollen aktiv sein, nur soll der Zugriff jeweils per volatile Variable erfolgen. Können wir irgendeine gesicherte Aussage über die Abfolge der gelesenen Werte machen?
2. Gleiches System, der schreibende Thread schreibt erst einen Wert (ständig). Von einem zufälligen Zeitpunkt an, schreibt er ständig einen zweiten Wert. Wie sieht es jetzt aus?
-
Volatile hilft bei Threads überhaupt nicht. In diesem Zusammenhang sollte es überhaupt nicht verwendet werden.
Siehe zum Beispiel die nicht so alte Diskussion:
http://groups.google.com/group/comp.programming.threads/browse_frm/thread/dee6c3e9af4124e6/79a4a0d192563109?lnk=st&q=volatile+comp.programming.threads#79a4a0d192563109
-
Angenommen wir haben diesen Pseudocode:
sig_atomic_t x = 0; void thread1() { for(size_t i = 0; i < 100; ++i) ++x; } void thread2() { for(size_t i = 0; i < 100; ++i) ++x; } int main() { using namespace std; CreateThread(thread1); CreateThread(thread2); // wenn beide threads fertig cout << x; }
Wäre x volatile, dann müsste 200 herauskommen, so kommen aber möglicherweise nur 100 raus. Ist jemand einverstanden?
-
Danke, für den Link. Hmm, ich hatte volatile mal genau so beschrieben gelesen. Naja man lernt nie aus
-
Es bleibt festzuhalten, dass Synchronisationsfunktionen einer Bibliothek (Locks) gänzlich andere Biester sind.
1. Eine Lockfunktion hat eine implizite Abhängigkeit von allen Speicherbereichen, auf die ein Thread zugreifen kann. Das heißt, dass alle Schreib- und Lesevorgänge vor dem Aufruf der Funktion auch vorher abgeschlossen sein müssen als auch darauffolgende Operationen auf Speicher nicht vor Rückkehr initiiert werden (es sei denn, ein bestimmter Speicherbereich ist garantiert alias-frei == thread-lokal). Diese Garantie stimmt mit volatile überein, soweit es Compileroptimierungen betrifft. ("vorher" und "nachher" bezieht sich hier auf die Ausführung innerhalb eines Threads).
2. Sie geht über volatile hinaus, als sie auch den internen Status der Maschine selbst betrifft (Zugriffe sind tatsächliche echt abgeschlossen, bevor der Synchronisationspunkt erreicht wird). Sie enthalten also das, was man als memory barrier bezeichnet. Es wird auch erreicht, dass diese Veränderungen global für jeden Thread sichtbar werden, der ebenfalls eine entsprechende Barriere erreicht (damit haben wir das Zeitproblem aber noch nicht gelöst - siehe Punkt 3 - wir garantieren nur, dass Änderungen in der Reihenfolge sichtbar sind, in der sie erfolgen, wobei Zwischenzustände ggf. übersprungen werden können).
3. sind (erfolgreiche) Lockoperationen relativ zu allen Threads total geordnet (außer ggf. bei R/W-Locks bei mehreren Lesezugriffen). Da wir bereits eine relative Ordnung innerhalb eines Threads erreicht haben, sorgt diese totale Ordnung dafür, dass alle Zugriffe (jedenfalls bzgl. des durch den Mutex geschützten Speichers) geordnet sind. Erst dadurch ist es überhaupt möglich, zu definieren, was der Zustand eines geschützten Objektes (nach erfolgreichen Lock) sein soll.
-
Stefan schrieb:
Angenommen wir haben diesen Pseudocode:
...
Wäre x volatile, dann müsste 200 herauskommen, so kommen aber möglicherweise nur 100 raus. Ist jemand einverstanden?
Abgelehnt. volatile sorgt nur dafür, dass irgendetwas zwischen 100 und 200 herauskommen muss.
-
Stefan schrieb:
Angenommen wir haben diesen Pseudocode:
Wäre x volatile, dann müsste 200 herauskommen, so kommen aber möglicherweise nur 100 raus. Ist jemand einverstanden?
Nein. Auch wenn x volatile ist kann da 100 herauskommen. Mit oder ohne ist es falsch.
-
-
Ponto schrieb:
Stefan schrieb:
Angenommen wir haben diesen Pseudocode:
Wäre x volatile, dann müsste 200 herauskommen, so kommen aber möglicherweise nur 100 raus. Ist jemand einverstanden?
Nein. Auch wenn x volatile ist kann da 100 herauskommen. Mit oder ohne ist es falsch.
Könntest du das begründen? Ist ++x nicht atomar?
-
camper schrieb:
1. Betrachte 2 Threads, einer schreibt permanent abwechselnd einen von zwei Werten an eine Speicherzelle; der andere liest permanent den Wert (wir nehmen an, dass beide Zugriffe "atomar" erfolgen, es wird also immer nur ein Wert gelesen, der so auch mal geschrieben wurde). Keine besonderen Synchronisationsmechanismen sollen aktiv sein, nur soll der Zugriff jeweils per volatile Variable erfolgen. Können wir irgendeine gesicherte Aussage über die Abfolge der gelesenen Werte machen?
2. Gleiches System, der schreibende Thread schreibt erst einen Wert (ständig). Von einem zufälligen Zeitpunkt an, schreibt er ständig einen zweiten Wert. Wie sieht es jetzt aus?Naja das ist mir schon klar denke ich.
Zum 1. - der zweite Thread wird irgendeine Folge der beiden Werte lesen. Manchmal den selben doppelt, manchmal "übersieht" er welche, manchmal stimmt die "Reihenfolge" zufällig. Das hängt allein vom Scheduler ab. Die gesicherte Aussage ist, dass wir mindestens einen der beiden Werte sehen werden. Im Worst-Case den anderen garnicht.
Zum . 2 - irgendwann, zu einem zufälligen Zeitpunkt, der nicht zeitgleich zum Zeitpunkt des Schreibens des anderen Wertes sein muss (und i.d.R. auch nicht ist), wird der zweite Thread den zweiten Wert lesen.
Meine Aussagen waren im Prinzip auch nicht direkt auf Threads bezogen, sondern auf asynchrone Änderungen, z.B. durch ein signal. Mein Verständnis von volatile war bisher immer, dass es dem Compiler sagt, dass er, obwohl eigentlich kein Aliasing vorliegen kann, trotzdem davon ausgeht, dass Aliasing vorliegt und er keine Optimierungen in diese Richtung macht.
-
Hihi.
Nein, x++ ist nicht atomar. Der C++ Standard kenn AFAIK (noch) nichtmal den Ausdruck atomar.Warum x++ nicht atomar ist? Einfach so, weil keiner sagt dass es atomar sein müsste. Beispiel:
load x to register increment register store register to x
Wo ist das jetzt Atomar?
Davon abgesehen, selbst wenn der erzeugte Assembler-Code einfach nur "increment x" wäre, selbst das wäre nicht notwendigerweise atomar. Ist es im übrigen auch nichtmal auf der "alles verzeihenden" x86 Architektur. Wäre VIEL zu aufwändig das überall zu garantieren, wo es doch in 99.9% der Fälle vollkommen überflüssig ist. Soll heissen selbst wenn nur ein einziger Assembler Befehlt irgendwo steht ist noch lange lange nicht garantiert dass die Durchführung dieses Befehls atomar ist. Im Gegenteil, die x86 Architektur kennt nur ganz wenige Befehle die man atomar machen kann, und nur einer davon ist es "von Haus aus" (ohne das "Lock Prefix").Meine Aussagen waren im Prinzip auch nicht direkt auf Threads bezogen, sondern auf asynchrone Änderungen, z.B. durch ein signal. Mein Verständnis von volatile war bisher immer, dass es dem Compiler sagt, dass er, obwohl eigentlich kein Aliasing vorliegen kann, trotzdem davon ausgeht, dass Aliasing vorliegt und er keine Optimierungen in diese Richtung macht.
volatile hat mit Aliasing nixe zu tun, volatile sagt dem Compiler bloss dass er einen Wert als "beobachtbar" behandeln soll, wobei jetzt "beobachtbar" auch bedeutet dass der Compiler davon ausgehen muss dass der Wert sich ohne das Zutun des Programmes ändert.
volatile verbietet allerdings viele Dinge eben grade NICHT, z.B. Reordering an vielen Stellen etc. volatile sorgt auch NICHT dafür dass Werte atomar geschrieben oder gelesen werden.
Und nochmal: wenn man Mutexen korrekt einsetzt braucht man weder volatile noch sonstige Tricks aus der Zauberkiste.
-
Ponto schrieb:
Volatile hilft bei Threads überhaupt nicht. In diesem Zusammenhang sollte es überhaupt nicht verwendet werden.
Siehe zum Beispiel die nicht so alte Diskussion:
http://groups.google.com/group/comp.programming.threads/browse_frm/thread/dee6c3e9af4124e6/79a4a0d192563109?lnk=st&q=volatile+comp.programming.threads#79a4a0d192563109Hallo,
wenn du mit "Zusammenhang" Synchronisierung meinst, dann bin ich einverstanden.
Sonst nicht.
Volatile funktioniert mit Threads und kann entsprechend seiner Bedeutung eingesetzt werden. Zum Beispiel um Signale an die Threads zu senden.jenz
-
jenz schrieb:
Volatile funktioniert mit Threads und kann entsprechend seiner Bedeutung eingesetzt werden.
Volatile hat in Bezug auf Threads keine Bedeutung, weil der Standard nichts über Nebenläufigkeit sagt. Das ist hier nun doch schon mehrfach gesagt worden.
Nur weil du einen Compiler benutzt, bei dem das trotzdem funktioniert, ist das nicht allgemeingültig.
jenz schrieb:
Zum Beispiel um Signale an die Threads zu senden.
Ja, mach du mal. Aber wundere dich nicht, wenn alles zusammenbricht, wenn du den Compiler wechselst.
-
dann erklär mir das doch bitte mal.
ein signal könnte zum beispiel sein, dass der thread sich beenden soll.
wenn ich also eine variablevolatile bool end;
habe, dann kann ich die innerhalb des threads abfragen und wenn end true ist, dann beendet sich der thread.
das ist das signal, das ich von einem anderen thread setzen kann.
wieso sollte das nicht immer gehen?
leider habe ich den standard nicht, aber ich finde auch nichts im netz, was dem widerspricht.
kann jemand den standard zitieren?jenz
-
@jenz: Sehe ich auch so. Allerdings sollte man wohl besser sig_atomic_t für das Signal nehmen.