Verständnisprobleme condition_variable
-
gfdsa schrieb:
manni66 schrieb:
Ich sehe kein sleep.
Was meinst du damit, übernimmt das denn nicht
cond.wait(...)?Wo steht das wait? Wo soll das sleep hin?
Spontan würde ich sagen, operating müsste beim Notify und in der Bedingungsprüfung der Condition Variable mit einbezogen werden, da der Consumer sonst am Ende im wait hängenbleiben könnte.
Die Bedingung der Schleife lautet doch entweder noch operieren oder noch was in der Liste. In der Liste wird doch sowieso noch was sein, entweder weil das Operieren noch nicht fertig ist oder weil noch was in der Liste ist. Warum sollte ich also auf
operatingüberprüfen? Wie soll da jemals ein Deadlock entstehen? Der Code funktioniert übrigens jedes Mal einwandfrei.- Producer sendet notify und wird unterbrochen (das kann das Betriebssystem jederzeit machen)
- Consumer wird geweckt, leert die Liste und legt sich wieder hin
- Producer läuft jetzt weiter und setzt operating auf false
- ?
Das ist kein Deadlock.
"funktioniert jedesmal" ist kein Korrektheitsbeweis.
Auch dieses Problem kann man mit einem sleep simulieren.
-
Ahja, stimmt.
Ansonsten ist alles korrekt so?Ich verstehe jedoch immer noch nicht was besser daran sein soll, condition_variable warten zu lassen, statt das Lock selbst. Gibt es dazu einen trifftigen Grund?
-
An dem Punkt waren wir schon.
-
gfdsa schrieb:
Ich verstehe jedoch immer noch nicht was besser daran sein soll, condition_variable warten zu lassen, statt das Lock selbst. Gibt es dazu einen trifftigen Grund?
Ich verstehe es auch nicht. Kann uns jemand aufklären?
-
manni66 schrieb:
An dem Punkt waren wir schon.
Also wegen der Prozessorauslastung? Aber es wird dann immerhin zweimal irgendwie "gewartet", nicht? Einmal beim Lock, wenns eben gelockt ist und einmal bei
cond.wait(...)? Wenns nicht wegen der Auslastung des Prozessors ist, habe ich das Ding noch nicht verstanden.Könntest du/irgendwer bitte etwas mehr Hintergrund dazu geben?
Ah, warte! Eine condition_variable wird dazu benutzt, um einen Thread zu blocken, bis ein spezifischer shared state modifiziert wurde (in meinem Fall list.size())? Ist das korrekt? Wenn man solch einen shared state also nicht brauch, bräuchte man auch keine condition_variable, richtig? Ergo, die condition_variable in meinem Beispiel war unnötig, weil der Consumer arbeitet so lange etwas in der Liste ist, oder operating true ist. Was dazu führt, dass alles abgearbeitet wird.
Kann das jemand bestätigen?
-
*push*
Kann bitte jemand meinen letzten Beitrag bestätigen?
Ich bin mir nicht sicher...
-
gfdsa schrieb:
Eine condition_variable wird dazu benutzt, um einen Thread zu blocken, bis ein spezifischer shared state modifiziert wurde (in meinem Fall list.size())? Ist das korrekt?
Ja - das ist richtig.
gfdsa schrieb:
Wenn man solch einen shared state also nicht brauch, bräuchte man auch keine condition_variable, richtig? Ergo, die condition_variable in meinem Beispiel war unnötig, weil der Consumer arbeitet so lange etwas in der Liste ist, oder operating true ist. Was dazu führt, dass alles abgearbeitet wird.
Das ist falsch, da die Consumer-Schleife auch dann noch duchlaufen wird, wenn '
operating==true' ist, aber die Liste noch leer ist, weil z.B. der Producer-Thread nicht nachkommt.
Das gilt gerade in Deinem Fall, da der Producer-Thead letzlich über das Betriebssystem auf die Festplatte zugreift, der wird also selbst - wenn auch nur kurz - wiederholt blockiert werden, während er seine Schleife abarbeitet. In dieser Zeit frisst Dein Consumer-Thread die gesamte CPU-Zeit (wenn er kann) und behindert damit u.a. einen potentiellen dritten Thread.
-
Tatsächlich, die Liste kann noch leer sein, wenn der Consumer die Schleife durchläuft, und darum die condition_variable, weil viele unnötige Schleifendurchläufe den Prozessor zu sehr belasten!
Ich hätte selbst darauf kommen müssen, danke für die Beiträge.
-
Wenn Du mit mehreren Threads arbeitest, so solltest Du einige Regeln beachten.
Eine davon ist: Arbeitest Du mit Mutexen und gibt es Variablen, auf die aus mehreren Threads heraus zugegriffen wird, so schützen sie IMMER und ÜBERALL durch ihren zugehörigen Mutex.In Deinem Fall brichst Du diese Regel gleich zweimal (ich beziehe mich auf das Listing im Beitrag von 19:51:21 am 23.07.2016).
- zum einen in Zeile 33 wo Du auf 'list.size()' zugreifst
- und zum anderen in Zeile 26 und 32 beim Zugriff auf 'operating'Der Zugriff auf 'list.size()' wird wohl in der Praxis immer funktionieren - d.h. es ist unsauber, aber sie wird IMHO zu keinem unerwünschten Verhalten des Programms führen.
Bei 'operating' wird's komplizierter. Du wirst wahrscheinlich denken, dass das ein bool ist, was eh' nur zwei Zustände annehmen kann und solange es nichtfalseist, ist estrue; und wenn der Producer es auffalsesetzt, ist sowieso alles gelaufen.
Das stimmt auch, aber stelle Dir mal vor, dass der Thread vom Producer genau vor Zeile 26 unterbrochen wird, und anschließend Consumer zum Zug kommt. Und zwar lange genug, um die letzte Liste abzuarbeiten und - daoperatingimmer noch true ist - wird sich der Consumer-Thread ggf. bis in Zeile 35 arbeiten und dort auf einnotifywarten. Und warten und warten ..., denn dasnotifykommt in diesem Fall nicht mehr!Du kannst dieses Verhalten provozieren, indem Du vor Zeile 21 (vor dem lock) und vor Zeile 26 (
operating = false) jeweils einen kurzen Sleep von wenigen Millisekunden einfügst. Der Thread des Consumers endet dann mit einem Deadlock.
Solche Fehler können in größeren Programmen zu einem echten (auch wirtschaftlichen!) Problem werden, da sie selten und vor allen nicht zuverlässig auftauchen. Das erschwert die Suche nach den Ursachen erheblich.Schützt Du die Abfrage des Flags
operatingauch mit dem Mutex, etwa so:for( bool local_operating_flag = true; local_operating_flag; ) { std::unique_lock<std::mutex> lock(mutex); cond.wait(lock, []{ return list.size(); }); paths.reserve(list.size()); std::copy(list.begin(), list.end(), std::back_inserter(paths)); list.clear(); local_operating_flag = operating; lock.unlock();.. dann wäre Dir nämlich spätestens nach dem ersten Test aufgefallen, dass im
cond.waitnoch was fehlt ...cond.wait(lock, []{ return !operating || list.size(); });und entsprechend auf der Producer-Seite:
std::unique_lock<std::mutex> lock(mutex); operating = false; cond.notify_one(); // notify*() ist NICHT threadsafe und muss mit dem Mutex geschützt werden!Bleibt vielleicht noch zu erwähnen, dass Du das ganze lieber in eine Klasse packen solltest, dann sind die globalen Variablen nicht mehr nötig. Alle jetzt globalen Variablen wären dann Member der Klasse.
Gruß
Werner
-
Nochmal zusammenfassen: Was sind die Vorteile von condition_variable gegenüber einem mutex?
-
nochmal schrieb:
Nochmal zusammenfassen: Was sind die Vorteile von condition_variable gegenüber einem mutex?
Was ist der Vorteil eines Schnitzels gegenüber einer Pfanne?
-
Hallo,
Tut mir Leid, wenn ich den Thread mal wieder hervorhebe, aber so alt ist er nicht und es geht noch um den gleichen, mit eurer Hilfe modifizierten Code.
Und zwar habe ich jetzt tatsächlich folgendes als Wait-Condition:
cond.wait(lock, []{ return !operating || list.size(); });Aber manchmal (echt nur manchmal) passiert trotzdem ein Deadlock und
consumer()returned nie mehr.Mit
cond.wait_for()jedoch klappt es, das ist aber eine hässliche Lösung, net?Ansonsten: Wie macht man das genau mit dem Producer/Consumer? Gibt es da einen exakten Weg, der immer funktioniert?