Muss man bei rein lesendem Zugriff auf einen elementaren Datentypen synchronisieren?
-
Und das const und & könnte man auch weglassen, nicht?
-
Was std::string und andere Library-Klassen angeht, kann man sich NUR danach richten, was die Library garantiert.
Der C++ Standard macht hier (noch) keine Garantien.
Also kann man sich nur nach der Doku derjenigen Implementierung richten, die man verwendet. -> das Programm ist dann nichtmehr portierbar (es sei denn man synchronisiert einfach alles).Das Problem ist, dass verschiedene Implementierungen unter "lesend zugreifen" was anderes verstehen. Oft ist es zwar so, dass alle "const" Memberfunktionen als "lesend" gelten, aber das ist nicht garantiert. Es gibt da etliche Beispiele für "const" Funktionen, die als "schreibend" gelten. Die ändern zwar nichts am beobachtbaren Zustand eines Objekts. Bloss werden u.U. "Innereien" verändert. Man denke bloss an Lazy-Evaluation.
Wird IMO echt langsam Zeit, dass der neue C++ Standard verabschiedet wird. Der wird dann (hoffentlich) endlich genaue Definitionen enthalten was in der Standard Library Thread-Safe zu sein hat, und was nicht.
-
Eine Frage noch:
Ich kann für eine Variable also InterlockedExchange() zum Schreiben verwenden, und fürs Lesen einfach normal benutzen, ohne Funktion, richtig?
Denn mit Interlocked..() wird ja atomic geschrieben, von daher sollte das Lesen immer sicher sein.
-
Die Variable sollte dann aber volatile deklariert sein, damit man beim Lesen auch wirklich immer den richtigen Wert bekommt, richtig?
-
Ich les mich grad ein wenig ein ( zB http://www.nntpnews.net/f1979/memory-barrier-volatile-c-5756173/index3.html ) und anscheinend braucht man nicht mal die Interlocked...()-Funktionen, sondern es reicht eine 32bit volatile-Variable? Visual Studio erzeugt dann automatisch eine memory barrier, sowohl beim Lesen als auch beim Schreiben...?
Stimmt das so? Wozu gibt es dann die Interlocked...()-Funktionen?
-
Damit klar wird was genau ich möchte:
Eine 32bit Variable in einem x86-kompilierten Programm auf Windows ausgeführt zwischen mehreren Threads synchronisieren.
Lesen/Schreiben aus unterschiedlichen Threads soll sich nicht in die Quere kommen, und gelesen werden soll immer der aktuelle Wert.Wie stelle ich das nun an?
-
Synchronisator schrieb:
Ich les mich grad ein wenig ein ( zB http://www.nntpnews.net/f1979/memory-barrier-volatile-c-5756173/index3.html ) und anscheinend braucht man nicht mal die Interlocked...()-Funktionen, sondern es reicht eine 32bit volatile-Variable? Visual Studio erzeugt dann automatisch eine memory barrier, sowohl beim Lesen als auch beim Schreiben...?
Stimmt das so? Wozu gibt es dann die Interlocked...()-Funktionen?
Visual C++ macht viel was der C++ Standard nicht vorschreibt.
Und die Interlocked Funktionen gibt es, um mehr "atomar" machen zu können, als nur Lesen *oder* Schreiben. z.B. Lesen+Vergleichen+Schreiben als eine atomare Operation.
Denn das geht mit volatile garnicht, weder mit Visual C++ noch mit sonst einem Compiler.
-
Also ich verwende jetzt InterlockedExchangeAdd(&variable, 0), um eine Variable zu lesen und InterlockedExchange(&variable, <neuerWert>), um eine Variable zu beschreiben, oder InterlockedIncrement(), um sie zu inkrementieren usw.
Ich denke, da mache ich nichts falsch, wenn ich diese Funktionen bei jedem Zugriff verwende.
Eine Frage noch:
Ich möchte auch float synchronisieren. Bin ich da mit Interlocked...() jetzt aufgeschmissen?
Muss ich da zB. eine critical section verwenden?
-
Ich verwende immer InterlockedCompareExchange(&var, 0, 0) zum Lesen.
InterlockedExchangeAdd muss natürlich genauso gehen, ist halt nur weniger üblich. (InterlockedCompareExchange hab ich schon öfters in fremdem Code gesehen, InterlockedExchangeAdd noch nie)
Grund ist vermutlich, dass InterlockedCompareExchange 1:1 dem CAS-Primitive entspricht, welches auf fast jeder Plattform aus nur einem Assembler-Befehl besteht. Für InterlockedExchangeAdd u.Ä. werden öfters mal Retry-Loops mit CAS verwendet, und die sind zum blossen Lesen natürlich total unnötig. Dazu kommt, dass etliche Interlocked Befehle unter Windows 95 nicht als KERNEL32.DLL Fuktionen verfügbar waren (sind erst später dazugekommen). InterlockedCompareExchange gab es dagegen "schon immer".Was float angeht:
float InterlockedReadFloat(float volatile* p) { // bitmuster lesen LONG l = InterlockedCompareExchange(reinterpret_cast<LONG volatile*>(p), 0, 0); // float draus machen float f; memcpy(&f, &l, 4); return f; } // bzw. float InterlockedReadFloatMightWorkToo(float volatile* p) { LONG l = InterlockedCompareExchange(reinterpret_cast<LONG volatile*>(p), 0, 0); return reinterpret_cast<float>(l); // bin mir aber nicht sicher ob das so hinhaut // garantiert ist es soweit ich weiss nicht, // aber ich schätze MSVC wird das machen was wir wollen }Und nochmal falls das unklar gewesen sein sollte: Intel "i386+" CPUs können DWORDs die auf 4 Bytes aligned sind OHNE Lock-Prefix "einfach so" atomar lesen und schreiben. Und MSVC garantiert obendrein noch, dass er selbst da nicht dreinpfuscht, wenn man volatile verwendet -- er macht aus volatile read/write sogar eine Full-Memory-Barrier.
Kurz gesagt: wenn bestimmte Voraussetzungen erfüllt sind, dann geht es sehrwohl nur mit "volatile". Aber eben nicht weil der Standard es garantiert, sondern weil Intel + Microsoft es für ganz bestimmte Produkte garantieren.
-
Ok, ich werde InterlockedCompareExchange() nutzen.
Nice, die float-Funktionen...
Ich dachte halt, dass die float-Informationen beim casten verloren gehen würden, aber wenn ich so nachdenke ist float ja auch 4 Bytes lang, also die Bits bleiben ja gleich.In der zweiten Funktion muss es aber ...
*reinterpret_cast<float*>(&l);
...heißen, dann funktioniert es. Einen cast von long nach float nimmt er nicht.
Für ne Inkrementierung reicht volatile ja nicht mehr, deshalb werde ich einfach die Interlocked-Funktionen verwenden.
Danke für die Tipps!
MfG
-
Dass es mit Pointer casten geht ist klar. Ist zwar auch nicht vom Standard garantiert, aber in diesem Fall denke ich zu verschmerzen.