Synchronisierung von Zugriffen auf gemeinsame Daten
-
Es gibt aber keine allgemeinen Regeln, es gibt nur Regeln für spezifische Systeme oder System-Gruppen wie z.B. Posix oder Windows oder ... .
Allgemein kann man nur sagen dass man nicht unbedingt davon ausgehen kann dass überhaupt irgendwas funktioniert sobald es mehr als einen Thread gibt
-
Es geht gar nicht um die Umsetzung, sondern nur darum, ob überhaupt eine Absicherung nötig ist, um race conditions u. ä. zu vermeiden.
-
synchronisator schrieb:
Es geht gar nicht um die Umsetzung, sondern nur darum, ob überhaupt eine Absicherung nötig ist, um race conditions u. ä. zu vermeiden.
bei einfachen flags, d.h. wenn eine simple änderung reicht und nur eine task schreibzugriffe macht, brauchst du keine synchronisation. wenn multiple tasks schreiben schon. auch wenn exakte werte gelesen werden sollen, weil's ja sein kann, dass die schreib-task bereits einen teil der variablen verändert hat und dann unterbrochen wird.
-
Super, genau das war die hilfreiche Antwort, auf die ich gehofft hatte
Danke @ ~fricky!
Im Prinzip handelt es sich beidem zweiten Fall um ein solches Flag, wenn ich das richtig sehe. Es wird nur abwechselnd gelesen bzw. geschrieben, was aber meines Erachtens nichts an der Thread-Sicherheit ändert.
Im konkreten Fall handelt es sich um Variablen mit der selben breite wie die Prozessorarchitektur. Von daher wird der Schreibvorgang selbst atomar durchgeführt
Angenommen, man möchte den ersten Fall synchronisieren - man möchte also, dass die beiden betreffenden Variablen zuerst geschrieben und dann erst ausgelesen werden. Beide Vorgänge finden periodisch statt, das Lesen allerdings 5 mal so häufig wie das Schreiben. Gibt es da eine elegante Lösung, um das zu synchronisieren?
-
synchronisator schrieb:
Im konkreten Fall handelt es sich um Variablen mit der selben breite wie die Prozessorarchitektur. Von daher wird der Schreibvorgang selbst atomar durchgeführt
darauf würde ich mich nicht verlassen. besser du schaust dir den output des compilers an, ob das auch wirklich so ist.
synchronisator schrieb:
Gibt es da eine elegante Lösung, um das zu synchronisieren?
die beste lösung ist doch, die synchronisationsfunktionen zu benutzen, die dein system/RTOS etc. bietet. die legen eine task temporär schlafen, wenn sie auf irgendwas warten muss und die frei werdende rechenzeit kommt anderen tasks zu gute. sich irgendwelche spin-locks selber zu bauen kann dagegen sehr ineffektiv sein.
-
synchronisator schrieb:
Ich hab hier eine Software mit im Wesentlichen zwei Threads, die eine Regelung realisieren. Dabei gibt es eine Variable, die von dem einen Thread geschrieben wird und von dem anderen ausgelesen.
Bisher war das ganze nicht synchronisiert. Wäre das nicht eigentlich notwendig? Für die Regelung wäre es ja besser, wenn zuerst ein neuer Wert geschrieben wird, bevor die Variable ausgelesen wird.
wenn nicht jeder wert den du setzt, sondern nur ein zustand der gerade abgefragt wird wichtig ist, brauchst du kein sync. ansonsten kannst du das mit hardware-synchronisationsprimitiven loesen. falls dir nicht nur richtiger programmablauf, sondern auch die geschwindigkeit dieser schnittstelle wichtig ist, kannst du sie mit einem commandbuffer realisieren.
Eine weitere Frage hätte ich noch zu einem ähnlichen Problem. Ich habe in dem Programm eine Variable erstellt, die abwechselnd von verschiedenen anderen Threads ausgewertet wird. Das sieht folgendermaßen aus:
volatile int SReady = 0;
Task 1:
if (SReady) { SReady = 0; ... }
Task 2 (wird nicht als erstes aktiviert):
SReady = 1;
Es funktioniert also so, dass Task 1 schon läuft, aber noch nichts macht. Sobald Task 2 läuft, gibt er ein Signal und Task 1 kann loslegen (durch ... symbolisiert).
Meine Frage ist nun: SReady ist ja eine gemeinsam genutzt Variable. Müsste man diese irgendwie synchronisieren?
da du schreibst, dass mehrere threads diesen wert auslesen, muss diese variable synchronisiert werden. sonst kann es passieren dass mehrere thread durch die if abfrage kommen, noch bevor dieser wert zurueckgeschrieben wurde.
-
Super, genau das war die hilfreiche Antwort, auf die ich gehofft hatte Danke @ ~fricky!
Die Antwort von fricky ist aber leider FALSCH.
Soll heissen: es gibt auch Systeme wo nichtmal der Zugriff auf "einfache Flags" ohne Synchronisation oder zusätzliche Barriers so funktioniert wie man es sich ganz naiv vorstellen würde. Kann z.B. sein dass Änderungen an dem Flag erst viel später von anderen Threads gesehen werden. Oder auch garnicht. Und natürlich dass Änderungen in der falschen Reihenfolge gesehen werde, und und und...
Verstehst du jetzt vielleicht etwas besser was ich dir mit meinem 1. Beitrag sagen wollte?
Es geht gar nicht um die Umsetzung, sondern nur darum, ob überhaupt eine Absicherung nötig ist, um race conditions u. ä. zu vermeiden.
Dann ist die Antwort, ein ganz klares "ja, immer!".
Erst wenn du dir ein spezifisches System anguckst kannst du sagen in welchen Fällen man nichts machen muss.Und... erst wenn du dir ein spezifisches System anguckst kannst du sagen *welche* Massnahmen an einer bestimmten Stelle nötig sind. Die Frage so allgemein zu stellen macht also IMO keinen Sinn.
Im konkreten Fall handelt es sich um Variablen mit der selben breite wie die Prozessorarchitektur. Von daher wird der Schreibvorgang selbst atomar durchgeführt
Wie kommst du auf die Idee dass jede CPU die es gibt Variablen mit "registerbreite" in einem Rutsch lesen/schreiben könnte? Und selbst WENN es nur ein Assembler-Befehl ist, und der Transfer vom/zum Speicher in einem einzigen Schritt gemacht wird - ist immer noch nicht garantiert dass auch alles funktioniert. z.B. wäre es ja schön wenn andere CPUs auch was von der Änderung mitbekommen, und möglichst auch gleich, nicht erst irgendwann. Was aber nicht garantiert ist.
-
hustbaer schrieb:
Kann z.B. sein dass Änderungen an dem Flag erst viel später von anderen Threads gesehen werden. Oder auch garnicht
das solltest du mal genauer erklären. vor allem das 'garnicht'. aber unter berücksichtigung, dass der OP seine variable 'volatile' deklariert hat (siehe erstes posting).
-
~fricky schrieb:
hustbaer schrieb:
Kann z.B. sein dass Änderungen an dem Flag erst viel später von anderen Threads gesehen werden. Oder auch garnicht
das solltest du mal genauer erklären. vor allem das 'garnicht'. aber unter berücksichtigung, dass der OP seine variable 'volatile' deklariert hat (siehe erstes posting).
volatile hat nicht viel mit synchronisation zu tun, es sagt dem compiler lediglich dass etwas nicht im register gelagert werden darf. hast du z.b. einen zweiten prozessor der garnichts vom cache des ersten prozessors weiss, hast du schon verloren. aber das passiert dir vermutlich nicht bei einer x86 architektur, wenn du unter windows auf anwendungsseite programmierst.
auf anderen systemen kann es eventuell sein dass du explizit die cpu pipeline flushen musst, den cache ebenfalls flushen oder sogar direkt am cache vorbeischreiben musst. aber da sollte man dann nicht mehr im forum fragen, sondern sich die paper der architektur durchlesen.
-
rapso schrieb:
volatile hat nicht viel mit synchronisation zu tun, es sagt dem compiler lediglich dass etwas nicht im register gelagert werden darf.
teilweise richtig. 'volatile' sagt einem standardkonformen c-compiler, dass auf jeden fall das 'physikalische objekt', also die speicherzelle bzw. der i/o-ports bei schreib- und lesezugriffen angesprochen werden muss. wenn auf einem multiprozessor-system ein cache-flush dazu gehört, dann muss auch der gemacht werden. mit synchronisation hat es zwar direkt nichts zu tun, aber 'volatile' sorgt zumindest dafür, dass änderungen des speichers für mehrere tasks bzw. mehrere prozessoren zeitnah sichtbar sind.
siehe auch: http://www.open-std.org/JTC1/SC22/wg14/www/docs/n897.pdf
-
~fricky schrieb:
hustbaer schrieb:
Kann z.B. sein dass Änderungen an dem Flag erst viel später von anderen Threads gesehen werden. Oder auch garnicht
das solltest du mal genauer erklären. vor allem das 'garnicht'. aber unter berücksichtigung, dass der OP seine variable 'volatile' deklariert hat (siehe erstes posting).
Nö, ich hab das schon so oft erklärt.
Lies doch einfach mal ein paar Artikel im Netz zu dem Thema, oder irgendwelche Papers.
Tip: es gibt Caches, und nicht alle CPUs halten die ohne Barriers/Fences/... so schön kohärent wie die x86 Familie.teilweise richtig. 'volatile' sagt einem standardkonformen c-compiler, dass auf jeden fall das 'physikalische objekt', also die speicherzelle bzw. der i/o-ports bei schreib- und lesezugriffen angesprochen werden muss. wenn auf einem multiprozessor-system ein cache-flush dazu gehört, dann muss auch der gemacht werden.
Rofl. Träum weiter. Und lies mal bitte den C++ Standard, der (C++ Standard) wird sich nämlich ordentlich wundern wenn du ihm das erzählst.
mit synchronisation hat es zwar direkt nichts zu tun, aber 'volatile' sorgt zumindest dafür, dass änderungen des speichers für mehrere tasks bzw. mehrere prozessoren zeitnah sichtbar sind.
Falsch.
siehe auch: http://www.open-std.org/JTC1/SC22/wg14/www/docs/n897.pdf
Und?
-
das ist C99 und nicht C++. Sprache wurde keine genannt, also gehe ich davon aus dass es vielleicht C++ sein könnte.
-
Der OP hat gesagt es geht ihm "nicht um eine spezifische Umsetzung für Windows/Posix oder was auch immer", also auch nicht um eine in Java oder speziellen Systemen die im Gegensatz zu C99 oder C++ ein Speichermodell vorschreiben welches sowas wie Threads überhaupt kennt.
Oder nochmal einfach: ohne ein "System" oder eine "Plattform" die definiert was "volatile" bedeutet hat "volatile" genau garkeine Bedeutung. -
Siehe (2): C99 und/oder C++ schreiben bezüglich volatile nichts vor was mit Threads irgendwie in Zusammenhang stehen würde. Ein Speichermodell mit Regeln für Multithreading ist für den neuen C++ Standard geplant, entsprechende Papers lassen sich ohne grossen Aufwand im Netz finden.
-
-
hustbaer schrieb:
- Siehe (2): C99 und/oder C++ schreiben bezüglich volatile nichts vor was mit Threads irgendwie in Zusammenhang stehen würde.
das stimmt zwar, sie schreiben aber vor, dass eine 'volatile' variable nicht gecached werden darf. aus dem von mir geposteten link: A volatile object is also an appropriate model for a variable shared among multiple processes.
hustbaer schrieb:
Oder nochmal einfach: ohne ein "System" oder eine "Plattform"
die definiert was "volatile" bedeutet hat "volatile" genau garkeine Bedeutung.was 'volatile' bedeutet, deiniert z.b. der C-standard, nicht das system. auf systemen, die sowieso nichts cachen, ist sowas wie 'volatile' natürlich überflüssig.
-
~fricky schrieb:
rapso schrieb:
volatile hat nicht viel mit synchronisation zu tun, es sagt dem compiler lediglich dass etwas nicht im register gelagert werden darf.
teilweise richtig. 'volatile' sagt einem standardkonformen c-compiler, dass auf jeden fall das 'physikalische objekt', also die speicherzelle bzw. der i/o-ports bei schreib- und lesezugriffen angesprochen werden muss. wenn auf einem multiprozessor-system ein cache-flush dazu gehört, dann muss auch der gemacht werden.
nein, das ist nicht was es bedeutet. ein compiler muss sich nicht darum kuemmert dass flushes gemacht werden oder am cache vorbei geschrieben wird.
deiner definition nach wuerde kein compiler der welt je standard conform werden koennen, weil keiner zur laufzeit des schlussendlichen binaries anhand der addresse wissen koennte wie er sich plattformspecifisch korrekt verhalten sollte.
mit synchronisation hat es zwar direkt nichts zu tun, aber 'volatile' sorgt zumindest dafür, dass änderungen des speichers für mehrere tasks bzw. mehrere prozessoren zeitnah sichtbar sind.
siehe auch: http://www.open-std.org/JTC1/SC22/wg14/www/docs/n897.pdf
nein, macht volatile nicht. lies dir dein pdf selbst genauer durch, volatile sorgt lediglich dafuer dass der compiler die variable nicht im register behaelt, sondern explizit immer zurueckschreibt bzw ausliest.
im falle von
static volatile int32_t test=1; . . . void funktion() { while(test) { }
wird dir ein compiler ohne volatile einfach eine endlosschleife generieren, das selbe gilt fuers schreiben, beim schreiben innerhalb der schleife, ohne test zu nutzen, wuerde er test nach der schleife erst aus dem register schreiben. mit volatile weist er den wert immer zu.
fuer ein pipeline flush auf simplen powerpc musst du dann noch __asm__ volatile("sync"); explizit angeben, danach eventuell das OS anweisen den cache zu flushen. auf mips musst du am cache vorbei schreiben indem du das oberste bit der addresse setzt. ... du bist ebenfalls dafuer verantwortlich, dass alles in der richtigen reihenfolge geschrieben wird, hast du mehrere variablen die in den speicher sollten, darfst du dich nicht drauf verlassen dass der compiler bzw die architektur sie in der im c source angegebenen reihenfolge machen, du musst das explizit sicherstellen, falls die reihenfolge wichtig ist, z.b. wenn du einen commandbuffer befuellst und dann den cmdb-pointer weiterschiebst.
-
rapso schrieb:
nein, das ist nicht was es bedeutet. ein compiler muss sich nicht darum kuemmert dass flushes gemacht werden oder am cache vorbei geschrieben wird.
deiner definition nach wuerde kein compiler der welt je standard conform werden koennen, weil keiner zur laufzeit des schlussendlichen binaries anhand der addresse wissen koennte wie er sich plattformspecifisch korrekt verhalten sollte.
Häh? Wozu brauche ich dann einen Compiler und 'volatile'? Der Compiler muß sich merken, ab wo es volatile ist und zur Runtime (falls Parameter weitergereicht werden) den Ursprung konsistent auflösen.
rapso schrieb:
im falle von
static volatile int32_t test=1; . . . void funktion() { while(test) { }
wird dir ein compiler ohne volatile einfach eine endlosschleife generieren, das selbe gilt fuers schreiben, beim schreiben innerhalb der schleife, ohne test zu nutzen, wuerde er test nach der schleife erst aus dem register schreiben. mit volatile weist er den wert immer zu.
Ich suche noch die Pointe ... ohne volatile persistentes static, Endlosschleife, ja klar. Ab
if (test) test = 0;
in dem while()- Block muß er test holen, auswerten und zurückschreiben. Nix Endlosschleife.
Aber eigentlich ist static volatile für Hardware gedacht, so daß sich test irgendwann von Außen ändern wird:A static volatile object is an appropriate model for a memory-mapped I/O register. Implementors of C translators should take into account relevant hardware details on the target systems when implementing accesses to volatile objects.
^^^^^^^^^^^^ (s.o. PDF)
rapso schrieb:
fuer ein pipeline flush auf simplen powerpc musst du dann noch __asm__ volatile("sync"); explizit angeben,
Nöh. Das müssen meine Compiler brav machen, sonst nicht Standard und auf OS zurechtkastriert und ab in die Mülltonne.
rapso schrieb:
danach eventuell das OS anweisen den cache zu flushen.
Hat mit volatile nicht soviel zu tun, wenn das OS aus Geschwindigkeitsgründen Inkonsistenzen riskiert. -> API
rapso schrieb:
auf mips musst du am cache vorbei schreiben indem du das oberste bit der addresse setzt. ... du bist ebenfalls dafuer verantwortlich, dass alles in der richtigen reihenfolge geschrieben wird, hast du mehrere variablen die in den speicher sollten, darfst du dich nicht drauf verlassen dass der compiler bzw die architektur sie in der im c source angegebenen reihenfolge machen, du musst das explizit sicherstellen, falls die reihenfolge wichtig ist, z.b. wenn du einen commandbuffer befuellst und dann den cmdb-pointer weiterschiebst.
Wenn ich einen nackigen Prozessor und 'nen C- Compiler habe, verlasse ich mich primär darauf, daß das so sequentiell wie in der Source abläuft mit entsprechender Vorsicht bei Hardwarezugriffen, die durch static volatile impliziert sind (jaja, die Precautions, die langen, die kruden).
Sonst könnte man ja gar nichts Deterministisches bauen.
-
Hallo,
also erst einmal danke für euren geballten Eifer
Ich muss das ganz doch etwas präzisieren: Die Sprache ist C, es handelt sich um ein Einprozessorsystem und Variablen, die entweder global sind oder auch nur einmal existieren, aber über Zeiger weitergereicht werden.
Meine Aussage bzgl. Atomarität war dann zu pauschal, was ich gemeint hatte war folgendes: Von dem verwendeten Compiler wird Assemblercode erzeugt, der neue Werte in einem Befehl schreibt (bzw. liest). Also kann höchstens ein veralteter Wert verwendet werden, in keinem Fall eine Mischung aus altem/neuen Low/High Nibble oder so.
Von daher denke ich, dass auf der beschriebenen Plattform keine Synchronisation nötig ist. Stimmt das so?
Zur zweiten Frage nochmal: Weiß jemand, ob bzw. wie Regler mit unterschiedlichen Abtastperioden normalerweise synchronisiert werden - man müsste ja für eine Synchronisierung im schnelleren Regler immer mitzählen, wieviele Perioden schon vergangen sind. Oder evtl. nen zählenden Semaphor verwenden.
-
rapso schrieb:
ein compiler muss sich nicht darum kuemmert dass flushes gemacht werden oder am cache vorbei geschrieben wird.
richtig, er muss sich nur darum kümmern, dass echte physikalische schreib/lese-zugriffe auf ein objekt stattfinden, bevor der code hinter dem nächsten sequenzpunkt ausgeführt wird, wenn das objekt als 'volatile' deklariert wurde. wie er das macht, bleibt ihm überlassen.
rapso schrieb:
deiner definition nach wuerde kein compiler der welt je standard conform werden koennen, weil keiner zur laufzeit des schlussendlichen binaries anhand der addresse wissen koennte wie er sich plattformspecifisch korrekt verhalten sollte.
das ist nicht meine definition, sondern die der c-standard-schreiberlinge. und wenn eine rechnerarchitektur das nicht zulässt, dann kann es für diesen rechner leider keinen compiler geben, der 'volatile' standardkonform unterstützt.
synchronisator schrieb:
Meine Aussage bzgl. Atomarität war dann zu pauschal, was ich gemeint hatte war folgendes: Von dem verwendeten Compiler wird Assemblercode erzeugt, der neue Werte in einem Befehl schreibt (bzw. liest). Also kann höchstens ein veralteter Wert verwendet werden, in keinem Fall eine Mischung aus altem/neuen Low/High Nibble oder so.
Von daher denke ich, dass auf der beschriebenen Plattform keine Synchronisation nötig ist. Stimmt das so?das kommt drauf an. in der regel tickert in solchen multitasking-systemen ein timer-interrupt, der's dem scheduler erlaubt, zwischen den tasks periodisch hin- und herzuschalten. ein interrupt kann normalerweise keinen angefangenen maschinenbefehl unterbrechen, sondern schlägt immer vorher oder hinterher zu. d.h. wenn bei dir z.b. 16-bit zugriffe in einem rutsch passieren und ein solcher mit einem maschinenbefehl gemacht wird, dann geht es. was aber sein kann ist z.b. dass der compiler einen vermeintlich 16-bittigen zugriff doch in 2 8-bit zugrffe zerlegt, weil die variable an einer ungeraden adresse beginnt und die cpu keine 16-bit zugriffe auf ungerade adressen machen kann. da musste mal in die doku deines systems und compilers gucken, um mehr rauszufinden.
-
~fricky schrieb:
rapso schrieb:
ein compiler muss sich nicht darum kuemmert dass flushes gemacht werden oder am cache vorbei geschrieben wird.
richtig, er muss sich nur darum kümmern, dass echte physikalische schreib/lese-zugriffe auf ein objekt stattfinden, bevor der code hinter dem nächsten sequenzpunkt ausgeführt wird, wenn das objekt als 'volatile' deklariert wurde. wie er das macht, bleibt ihm überlassen.
'echte' ist eine schwammige behauptung von dir.
genau gesagt, ich wiederhole mich gerne, darf er den wert nicht nur im register behalten. ob dieser schreibvorgang 'echt' im physikalischem speicher ankommt ist nicht mehr sache des compilers.rapso schrieb:
deiner definition nach wuerde kein compiler der welt je standard conform werden koennen, weil keiner zur laufzeit des schlussendlichen binaries anhand der addresse wissen koennte wie er sich plattformspecifisch korrekt verhalten sollte.
das ist nicht meine definition, sondern die der c-standard-schreiberlinge. und wenn eine rechnerarchitektur das nicht zulässt, dann kann es für diesen rechner leider keinen compiler geben, der 'volatile' standardkonform unterstützt.
das ist deine interpretation davon, wenn das so waere, braeuchte man keine fences (und die gibt es selbst auf x86.
-
Auch ein interessanter Einwand. Laut Datenblatt der CPU werden solche Zugriffe durch die Speicherschnittstelle entsprechend verschoben. Von daher passt wohl alles
-
rapso schrieb:
das ist deine interpretation davon, wenn das so waere, braeuchte man keine fences (und die gibt es selbst auf x86.
aber 'fences' beissen sich doch nicht mit dem, was ich hier erzähle. 'volatile' sorgt dafür, dass zugriffe auch tatsächlich stattfinden und der zugriff bei beginn der nächsten anweisung (in derselben task) vollständig abgeschlossen ist. in welcher zeitlichen reihenfolge andere tasks diese zugriffe wahrnehmen (weil z.b. die hardware buszugriffe optimiert), wird nicht durch volatile geregelt. darüber steht nichts im C-standard (und im entsprechenden dokument von C's fettleibiger schwester sicherlich auch nicht). was aber volatile (als logische konsequenz) sicherstellt ist, dass änderungen eines volatile-objekts in anderen tasks sichtbar sind, weil beide auf dieses zielobjekt auch wirklich zugreifen müssen.
-
synchronisator schrieb:
Ich muss das ganz doch etwas präzisieren: Die Sprache ist C, es handelt sich um ein Einprozessorsystem und Variablen, die entweder global sind oder auch nur einmal existieren, aber über Zeiger weitergereicht werden.
Das ist ungünstig, weil Du nur zur Compiletime "volatile" aufgelöst bekommst.
synchronisator schrieb:
Also kann höchstens ein veralteter Wert verwendet werden, in keinem Fall eine Mischung aus altem/neuen Low/High Nibble oder so.
Die Frage ist, wie portabel das werden soll. Mach 'ne Note rein oder eine ordentliche Doppelabfrage.
synchronisator schrieb:
Von daher denke ich, dass auf der beschriebenen Plattform keine Synchronisation nötig ist. Stimmt das so?
Der geschilderte 2- Thread Mechanismus klingt sauber soweit.
synchronisator schrieb:
Zur zweiten Frage nochmal: Weiß jemand, ob bzw. wie Regler mit unterschiedlichen Abtastperioden normalerweise synchronisiert werden - man müsste ja für eine Synchronisierung im schnelleren Regler immer mitzählen, wieviele Perioden schon vergangen sind. Oder evtl. nen zählenden Semaphor verwenden.
Ei, ei, ei, richtig, isochrone Algorithmen und Multitasking vertragen sich eigentlich nicht, ich denke, Du solltest mehr in medias res gehen - also, was genau brauchst Du?