Hallo,
ein Trigger-Thread soll ein paar Worker-Threads per condition_variable
triggern sobald std::cin.get() per Tastatur ausgelöst wird.
Das Problem ist, dass wenn die Worker_Threads den ersten Trigger
bekommen, dann in der while(1) Schleife beim nächsten condVar.wait nicht
mehr blockiert wird.
Eigentlich sollte nach einem Trigger alles Worker_Threads bei
condVar.wait wieder auf einen neuen Trigger warten. Das tun sie aber
nicht und die Schleife rennt durch als gäbe es keine Blockierung.
Wenn ich das Prädikat []{return dataReady;} rauslasse ist das Problem
sofort verschwunden. Allerdings soll man wegen der "spurious wakeups"
nicht auf das Prädikat verzichten. Mit dem Prädikat habe ich allerdings
das genannte Problem.
Hat jemand eine Idee wie man da weiter kommt?
Normalerweise stellt man da eine wasserfeste Auftragsqueue in den
Mittelpunkt. Das wait() und notify() teilt dann den Workern nur mit, sie
sollen mal nachschauen, ob wieder was in der Queue ist.
So etwas selbst wasserfest bekommen? Besser nach multithreaded work
queue googeln.
Mit dem Prädikat würde im Prinzip alles wasserfest sein, nur sorgt es
dafür, dass das Blockieren nicht mehr funktioniert.
Was ich völlig unlogisch finde ist, dass ohne das Prädikat bei wait()
alles wunderbar blockiert aber mit dem Prädikat eben nicht.
Daraus lässt sich schließen, dass das Prädikat irgendwas am wait()
manipuliert.
So wie ich es verstanden habe, sollte das Prädikat eine zusätzliche
Bedingung sein um "spurious wakeups" zu unterbinden. Aber wieso wird
durch diese optionale Prädikat das wait() völlig ausgehebelt?
Man braucht nur
1
condVar.wait(lck,[]{returndataReady;});
durch
1
condVar.wait(lck);
zu ersetzen und alles funktioniert wunderbar.
Das Dilemma:
- ohne Prädikat kann es zu "spurious wakeups" kommen.
- mit Prädikat funktioniert wait() nicht mehr.
Mit dataReady auf false stellen bin ich auch nicht weitergekommen, denn
es ist die Frage wo auf false stellen?
Wenn man dataReady hinter wait() auf false stellt wird nur ein Thread
getriggert und die anderen werden ausgesperrt. Es sollen aber diverse
Threads getriggert werden.
Wenn man dataReady hinter notify_all() stellt wird überhaupt kein Thread
mehr getriggert.
> dataReady sollte wieder auf false gesetzt werden.
Von welchem Worker Thread? Und wie soll es koordiniert werden, damit
nach einem notify_all() alle Thread genau einmal laufen? Und was machst
du wenn ein Worker noch läuft, während das nächste notify_all() gesendet
wird...
>Mit dem Prädikat würde im Prinzip alles wasserfest sein
Mit Try&Error bekommst du es niemals wasserfest. Du kannst es nicht
testen ob alle Aktionen, die atomar sein müssen, unter keinen Umständen
unterbrochen werden.
So wie Du es umgesetzt hast, gibt es keine suprious wakeups ... da hast
Du Du den Begriff falsch verstanden.
Ausserdem hast Du die Bedeutung des Prädikates falsch verstanden:
So steht es auch in der Doku:
1
while(!pred()){
2
wait(lock);
3
}
Du muss Dich entscheiden, ob einer / alle Threads aufgeweckt werden
sollen. Und Du muss eine Wartebedingung formulieren für das Warte-Idiom.
Ja klar es gibt keine suprious wakeups weil ich mich ja im Code mit der
weitere Bedingung gesichert habe. Nur sorgt es dafür, dass das
Ereignisgesteuerte Triggern der Worker_Threads NICHT mehr funktioniert.
Wie gesagt das Prädikat hab ich nur reingemacht weil nach zB folgenden
Quellen gesagt wird es gibt sonst "Zufälliges Aufwachen" spurious
wakeup.
http://de.cppreference.com/w/cpp/thread/condition_variable/waithttp://www.grimm-jaud.de/index.php/blog/bedingungsvariablen
Ohne Prädikat geht ja alles wie gewollt.
Das Ziel ist ganz einfach:
Der Trigger_Thread soll ALLE Worker_Threads nach einem Ereignis
triggern.
In diesem Fall ist das Ereignis std::cin.get();
In den Worker_Threads würde bei "// work work work" eine Aufgabe
abgearbeitet werden. Danach startet die Schleife neu und soll bei wait()
erneut auf ein neues Signal warten. Und genau das Warten funktioniert
durch die Verwendung des Prädikates NICHT mehr.
Wenn alle Worker-Threads jedes Ereignis genau einmal verarbeiten sollen,
reicht ein Boolean als Prädikat nicht, denn das Boolean muss ja wieder
zurückgesetzt werden, nachdem das Ereignis verarbeitet wurde.
Das mindeste wäre ein Counter, der vom Trigger_Thread auf 5 gesetzt wird
und von jedem Worker_Thread dekrementiert. Wenn er auf 0 ist, haben alle
Worker_Threads das Ereignis verarbeitet. Danach kann der Trigger_Thread
das nächste Zeichen empfangen. Du bräuchtest dann noch eine zweite
Condition-Variable, mit der die Worker_Threads dem Trigger_Thread
signalisieren, dass sie fertig sind.
Die Lösung ist allerdings nur eingeschränkt parallel. Ein Ereignis wird
zwar parallel von mehreren Threads bearbeitet, es kann aber nicht
parallel schon das nächste empfangen und teilweise verarbeitet werden.
Wenn man mehr parallelisieren möchte, könnte man für jeden Worker_Thread
eine eigene Queue anlegen, aus der er seine Arbeit holt. Der
Trigger_Thread müsste dann jedes empfangene Ereignis in alle Queues
legen. Dadurch kann der Trigger_Thread unabängig von den Worker_Threads
neue Zeichen empfangen.
Am Ende kommt es drauf an, was Du überhaupt parallelisieren willst. Die
einfachere Variante wäre, dass nicht alle 5 Threads das Ereignis
bearbeiten müssen, sondern ein Thread alle Verarbeitungsschritte
erledigt. Stattdessen könntest Du mehrere verschiedene Ereignisse
gleichzeitig bearbeiten. Wenn beispielsweise "Hallo" empfangen wird,
bearbeitet Thread 1 das "H", Thread 2 das "a", Thread 3 das "l" usw.
Ich hab jetzt eine funktionierende Lösung entwickelt ohne auf das
Prädikat zu verzichten. Und zwar bekommt einfach jeder Workerthread
seine eigene condition_variable, mutex und bool.
Der Trigger_Thread ruft dann per notify die Worker auf die in der
Datenstrukturliste sind. Damit kann jeder Worker sein eigenes bool
(dataReady) auf false stellen und das Prädikat ist erfüllt.
> Es kommt nicht "einfach so" zu wake-ups ;-)
Das ist interessant. Es kann tatsächlich sein dass ich es nicht
verstanden habe aber alle Quellen sagen, dass wake-ups und auch lost
wakeup tatsächlich "zufällig" oder besser gesagt nicht gewollt auftreten
kommen können.
Zitat von hier:
https://www.video2brain.com/de/tutorial/sender-und-empfaenger
1
"So genannte spurious wakeup und lost wakeup. Was ist ein spurious wakeup? Da verwende ich eine Analogie. Es kann durchaus passieren, wenn ein Thread auf einen anderen Thread wartet, und er dann die Benachrichtigung erhält, dass der wartende Thread vermeintlich die Benachrichtigung bekommen hat, tatsächlich war aber das keine Benachrichtigung. Das ist wie, wenn Sie im Bett legen, und die Katze kratzt an der Tür, und Sie glauben, es war der Wecker. Das ist just Hintergrund-Geräusch. Das ist das Phänomen des spurious wakeup. Da gibt es noch das Phänomen des lost wakeup. Das lost wakeup ist das Phänomen, dass Ihr Wecker läutet und Sie sind noch gar nicht in dem Zustand, auf den Wecker zu hören. Das heißt zum Beispiel, Ihr Wecker läutet, bevor Sie ins Bett gehen. Das ist lost wakeup. Sie schlafen dann für ewig. Anders ausgedrückt, es werden nicht schöne Arten, den Lock zu produzieren. Gut, und weil es diese Phänomene gibt, schützt man sich mit dem Prädikat dagegen."
Threading_Guru schrieb:> Ich hab jetzt eine funktionierende Lösung entwickelt ohne auf das> Prädikat zu verzichten. Und zwar bekommt einfach jeder Workerthread> seine eigene condition_variable, mutex und bool.
Sein eigenen mutex? Dann ist doch der Sinn des Mutex wohl ad absurdum
geführt ...
>> Es kommt nicht "einfach so" zu wake-ups ;-)>> Das ist interessant. Es kann tatsächlich sein dass ich es nicht> verstanden habe aber alle Quellen sagen, dass wake-ups und auch lost> wakeup tatsächlich "zufällig" oder besser gesagt nicht gewollt auftreten> kommen können.
An einem Digital-Rechner ist nichts zufällig ... ausser /dev/random ;-)
Gemeint ist mit "spurious wakeups" ein Performanceproblem, kein
semantisches Problem (Manchmal hat das Problem auch den Namen
"thundering herd").
Stell Dir vor, Du hast einen signalisierenden AT (Aktivitätsträger,
Thread) und mindestens 2 wartende ATs. Die Wartebedingungen der
wartenden ATs sind unterschiedlich, aber überlappend: also muss(!) man
ein notifiy_all() statt eines notify_one() verwenden (eine CV mit
unterschiedlichen, überlappende Wartebedingungen verlangen ein
notify_all()) (überlappende Bedingungen sind Bedingungen, die
gleichzeitig wahr / falsch werden können).
AT1
1
while(i<0){cv.wait();}
AT2
1
while(i<5){cv.wait();}
Wenn nun durch den signalisierenden AT bspw. i = 2 gesetzt wird, dann
müssen ja beide wartenden ATs aufgeweckt werden(s.o., sonst kann es auch
sein, dass der "falsche" AT aufgeweckt wird und nicht das passiert, was
man semantisch will. Aus Sicht des Applikationsprogrammierers
"erscheint" die Auswahl eines AT bei einem notify_one() zufällig, was
aber bezogen auf das Gesamtsystem natürlich nicht ist), allerdings kann
nur AT1 das Warteidiom überwinden, also weiterlaufen. AT2 legt sich
sofort wieder schlafen. Für AT2 ist es ein "spurious wakeup".
Wilhelm M. schrieb:> Threading_Guru schrieb:>> Ich hab jetzt eine funktionierende Lösung entwickelt ohne auf das>> Prädikat zu verzichten. Und zwar bekommt einfach jeder Workerthread>> seine eigene condition_variable, mutex und bool.>> Sein eigenen mutex? Dann ist doch der Sinn des Mutex wohl ad absurdum> geführt ...
Nein, überhaupt nicht. Der Haupt-Thread muss ja auch noch mit den
Worker-Threads kommunizieren, und das geht mit dem eigenen bool pro
Worker-Thread, die jeder von einem Mutex geschützt werden müssen. Die
gewählte Lösung macht sehr, sehr viel Sinn!
tictactoe schrieb:> Wilhelm M. schrieb:>> Threading_Guru schrieb:>>> Ich hab jetzt eine funktionierende Lösung entwickelt ohne auf das>>> Prädikat zu verzichten. Und zwar bekommt einfach jeder Workerthread>>> seine eigene condition_variable, mutex und bool.>>>> Sein eigenen mutex? Dann ist doch der Sinn des Mutex wohl ad absurdum>> geführt ...>> Nein, überhaupt nicht. Der Haupt-Thread muss ja auch noch mit den> Worker-Threads kommunizieren,
das wissen wir mal wieder nicht so genau.
> und das geht mit dem eigenen bool pro> Worker-Thread, die jeder von einem Mutex geschützt werden müssen.
Wie oben gesagt, braucht man die bool'sche flags wahrscheinlich gar
nicht.
Vielleicht macht für sein Problem ein Counting-Semaphor mehr Sinn ...
das hängt von seiner Definition des Begriffes "triggern" ab ;-)
Wilhelm M. schrieb:> Threading_Guru schrieb:>> Ich hab jetzt eine funktionierende Lösung entwickelt ohne auf das>> Prädikat zu verzichten. Und zwar bekommt einfach jeder Workerthread>> seine eigene condition_variable, mutex und bool.>> Sein eigenen mutex? Dann ist doch der Sinn des Mutex wohl ad absurdum> geführt ...
Nein, denn das Mutex schützt das boolsche Prädikat. Wenn jeder Thread
sein eigenes Bool-Flag hat, kann auch jeder sein eigenes Mutex haben.
> An einem Digital-Rechner ist nichts zufällig ... ausser /dev/random ;-)>> Gemeint ist mit "spurious wakeups" ein Performanceproblem, kein> semantisches Problem (Manchmal hat das Problem auch den Namen> "thundering herd").>> [...] Aus Sicht des Applikationsprogrammierers> "erscheint" die Auswahl eines AT bei einem notify_one() zufällig, was> aber bezogen auf das Gesamtsystem natürlich nicht ist), allerdings kann> nur AT1 das Warteidiom überwinden, also weiterlaufen. AT2 legt sich> sofort wieder schlafen. Für AT2 ist es ein "spurious wakeup".
Nein, das hat damit überhaupt nichts zu tun. Die Condition-Variable weiß
ja nichts über das Prädikat. Sie ist einfach ein Objekt, das bei Aufruf
von wait() den Thread schlafen legt, bis ein anderer Thread notify()
aufruft. Manchmal kehrt wait() aber auch "einfach so" zurück, ohne dass
jemand notify() auf dem Objekt aufgerufen hat.
Dass man die Condition-Variable sinnvollerweise zusammen mit einem
Prädikat in einer Schleife verwendet, ist die Konsequenz daraus.
Semantisch ist das wie eine Busy-Waiting-Schleife auf das Prädikat. Der
Unterschied ist nur, dass der Aufruf von wait() dazu führt, dass bis zum
nächsten Aufwachen des Threads keine CPU-Zyklen verbraten werden. Ob das
Prädikat beim Aufwachen erfüllt ist, ist nicht Teil der Semantik der
Condition-Variable.