Sie müssen beim Anrufen keine Sperre halten condition_variable::notify_one()
, aber es ist nicht falsch in dem Sinne, dass es sich immer noch um ein genau definiertes Verhalten handelt und nicht um einen Fehler.
Es kann sich jedoch um eine "Pessimisierung" handeln, da jeder wartende Thread (falls vorhanden) sofort versucht, die Sperre zu erlangen, die der benachrichtigende Thread hält. Ich denke, es ist eine gute Faustregel, das Sperren einer Bedingungsvariablen beim Aufrufen von notify_one()
oder zu vermeiden notify_all()
. Siehe Pthread Mutex: pthread_mutex_unlock () benötigt viel Zeit für ein Beispiel, bei dem das Aufheben einer Sperre vor dem Aufrufen des pthread-Äquivalents einer notify_one()
verbesserten Leistung messbar ist.
Beachten Sie, dass der lock()
Aufruf in der while
Schleife irgendwann erforderlich ist, da die Sperre während der while (!done)
Überprüfung des Schleifenzustands gehalten werden muss. Aber es muss nicht für den Anruf gehalten werden notify_one()
.
2016-02-27 : Großes Update, um einige Fragen in den Kommentaren zu beantworten, ob es eine Rennbedingung gibt, ist die Sperre keine Hilfe für den notify_one()
Anruf. Ich weiß, dass dieses Update zu spät ist, da die Frage vor fast zwei Jahren gestellt wurde, aber ich möchte die Frage von @ Cookie nach einer möglichen Rennbedingung beantworten, wenn der Hersteller ( signals()
in diesem Beispiel) notify_one()
kurz vor dem Verbraucher ( waits()
in diesem Beispiel) anruft anrufen können wait()
.
Der Schlüssel ist, was passiert i
- das ist das Objekt, das tatsächlich anzeigt, ob der Verbraucher "Arbeit" zu tun hat oder nicht. Dies condition_variable
ist nur ein Mechanismus, mit dem der Verbraucher effizient auf eine Änderung warten kann i
.
Der Hersteller muss die Sperre beim Aktualisieren halten i
, und der Verbraucher muss die Sperre beim Überprüfen i
und Aufrufen halten condition_variable::wait()
(wenn er überhaupt warten muss). In diesem Fall ist der Schlüssel, dass es sich um dieselbe Instanz des Haltens des Schlosses handeln muss (oft als kritischer Abschnitt bezeichnet), wenn der Verbraucher dieses Check-and-Wait durchführt. Da der kritische Abschnitt gehalten wird, wenn der Hersteller aktualisiert i
und wenn der Verbraucher eincheckt und wartet i
, besteht keine Möglichkeit i
, zwischen dem Zeitpunkt, zu dem der Verbraucher prüft, i
und dem Zeitpunkt, zu dem er anruft, zu wechseln condition_variable::wait()
. Dies ist der springende Punkt für die ordnungsgemäße Verwendung von Bedingungsvariablen.
Der C ++ - Standard besagt, dass condition_variable :: wait () sich beim Aufruf mit einem Prädikat (wie in diesem Fall) wie folgt verhält:
while (!pred())
wait(lock);
Es gibt zwei Situationen, die auftreten können, wenn der Verbraucher prüft i
:
Wenn i
0 ist, ruft der Verbraucher auf cv.wait()
, dann i
ist er immer noch 0, wenn der wait(lock)
Teil der Implementierung aufgerufen wird - die ordnungsgemäße Verwendung der Sperren stellt dies sicher. In diesem Fall hat der Produzent keine Möglichkeit, das condition_variable::notify_one()
in seiner while
Schleife aufzurufen , bis der Verbraucher angerufen hat cv.wait(lk, []{return i == 1;})
(und der wait()
Anruf alles getan hat, um eine Benachrichtigung ordnungsgemäß zu "fangen") - wait()
die Sperre wird erst freigegeben, wenn er dies getan hat ). In diesem Fall kann der Verbraucher die Benachrichtigung nicht verpassen.
Wenn i
beim Aufrufen des Verbrauchers bereits 1 angegeben ist cv.wait()
, wird der wait(lock)
Teil der Implementierung niemals aufgerufen, da der while (!pred())
Test dazu führt, dass die interne Schleife beendet wird. In dieser Situation spielt es keine Rolle, wann der Aufruf von notify_one () erfolgt - der Verbraucher blockiert nicht.
Das Beispiel hier hat die zusätzliche Komplexität, die done
Variable zu verwenden, um dem Produzenten-Thread zu signalisieren, dass der Konsument dies erkannt hat i == 1
, aber ich denke, dies ändert die Analyse überhaupt nicht, da der gesamte Zugriff auf done
(sowohl zum Lesen als auch zum Ändern) ) werden in den gleichen kritischen Abschnitten durchgeführt, in denen i
und die condition_variable
.
Wenn man sich die Frage suchen , dass @ EH9 zeigte auf, ist Sync unzuverlässig mit std :: Atom und std :: condition_variable , Sie werden eine Race - Bedingung sehen. Der in dieser Frage veröffentlichte Code verstößt jedoch gegen eine der Grundregeln für die Verwendung einer Bedingungsvariablen: Er enthält keinen einzigen kritischen Abschnitt, wenn ein Check-and-Wait durchgeführt wird.
In diesem Beispiel sieht der Code folgendermaßen aus:
if (--f->counter == 0) // (1)
// we have zeroed this fence's counter, wake up everyone that waits
f->resume.notify_all(); // (2)
else
{
unique_lock<mutex> lock(f->resume_mutex);
f->resume.wait(lock); // (3)
}
Sie werden feststellen, dass das wait()
bei # 3 ausgeführt wird, während Sie gedrückt halten f->resume_mutex
. Die Prüfung, ob das wait()
in Schritt 1 erforderlich ist oder nicht, wird jedoch nicht durchgeführt, während diese Sperre überhaupt gehalten wird (viel weniger kontinuierlich für das Prüfen und Warten), was eine Voraussetzung für die ordnungsgemäße Verwendung von Bedingungsvariablen ist. Ich glaube, dass die Person, die das Problem mit diesem Code-Snippet hat, dachte, dass dies die Anforderung erfüllen würde , da f->counter
es sich um einen std::atomic
Typ handelt. Die von bereitgestellte Atomizität std::atomic
erstreckt sich jedoch nicht auf den nachfolgenden Aufruf von f->resume.wait(lock)
. In diesem Beispiel gibt es einen Wettlauf zwischen dem Zeitpunkt, zu dem f->counter
das Kontrollkästchen aktiviert ist (Schritt 1) und dem Zeitpunkt, zu dem wait()
das aufgerufen wird (Schritt 3).
Diese Rasse existiert im Beispiel dieser Frage nicht.
wait morphing
Optimierung zu aktivieren ) Faustregel, die in diesem Link erläutert wird: Benachrichtigen mit Sperrung ist in Situationen mit mehr als 2 Threads besser, um vorhersehbarere Ergebnisse zu erzielen.the_condition_variable.wait(lock);
. Wenn zum Synchronisieren von Produzent und Konsument keine Sperre erforderlich ist (z. B. ist der Basiswert eine sperrenfreie spsc-Warteschlange), hat diese Sperre keinen Zweck, wenn der Produzent sie nicht sperrt. Für mich in Ordnung. Aber besteht nicht das Risiko für ein seltenes Rennen? Wenn der Produzent die Sperre nicht hält, kann er dann nicht notify_one aufrufen, während der Konsument kurz vor dem Warten ist? Dann wartet der Verbraucher und wacht nicht auf ...std::cout << "Waiting... \n";
während der Produzent dies tutcv.notify_one();
. Dann geht der Weckruf verloren ... Oder fehlt mir hier etwas?Lage
Mit vc10 und Boost 1.56 habe ich eine gleichzeitige Warteschlange implementiert, so wie es dieser Blog-Beitrag vorschlägt. Der Autor entsperrt den Mutex, um Konflikte zu minimieren, dh er
notify_one()
wird mit entsperrtem Mutex aufgerufen:Das Entsperren des Mutex wird durch ein Beispiel in der Boost-Dokumentation unterstützt :
Problem
Dies führte jedoch zu folgendem unberechenbarem Verhalten:
notify_one()
noch nicht aufgerufen wurdecond_.wait()
kann noch über unterbrochen werdenboost::thread::interrupt()
notify_one()
wurde zum ersten Malcond_.wait()
Deadlocks genannt; das Warten nicht durch beendet werdenboost::thread::interrupt()
oderboost::condition_variable::notify_*()
mehr.Lösung
Durch Entfernen der Zeile
mlock.unlock()
funktionierte der Code wie erwartet (Benachrichtigungen und Interrupts beenden das Warten). Beachten Sie, dass dernotify_one()
aufgerufene Mutex beim Verlassen des Gültigkeitsbereichs sofort danach entsperrt wird:Das bedeutet, dass zumindest bei meiner speziellen Thread-Implementierung der Mutex vor dem Aufruf nicht entsperrt werden darf
boost::condition_variable::notify_one()
, obwohl beide Möglichkeiten korrekt erscheinen.quelle
Wie andere bereits betont haben, müssen Sie beim Anrufen die Sperre nicht gedrückt halten, was die
notify_one()
Rennbedingungen und Threading-Probleme betrifft. In einigen Fällen kann es jedoch erforderlich sein, das Schloss zu halten, um zu verhindern, dass das Schlosscondition_variable
zerstört wird, bevornotify_one()
es aufgerufen wird. Betrachten Sie das folgende Beispiel:Angenommen, es gibt einen Kontextwechsel zum neu erstellten Thread,
t
nachdem wir ihn erstellt haben, aber bevor wir auf die Bedingungsvariable warten (irgendwo zwischen (5) und (6)). Der Threadt
erfasst die Sperre (1), setzt die Prädikatvariable (2) und gibt dann die Sperre (3) frei. Angenommen, es gibt an dieser Stelle einen weiteren Kontextwechsel, bevornotify_one()
(4) ausgeführt wird. Der Hauptthread erhält die Sperre (6) und führt die Zeile (7) aus. An diesem Punkt kehrt das Prädikat zurücktrue
und es besteht kein Grund zum Warten. Daher wird die Sperre aufgehoben und fortgesetzt.foo
return (8) und die Variablen in ihrem Bereich (einschließlichcv
) werden zerstört. Bevor der Threadt
dem Hauptthread (9) beitreten kann, muss er seine Ausführung beenden, damit er dort fortfährt, wo er für die Ausführung aufgehört hatcv.notify_one()
(4), an welchem Punktcv
ist bereits zerstört!Die mögliche Lösung in diesem Fall besteht darin, die Sperre beim Aufrufen
notify_one
gedrückt zu halten (dh den Bereich zu entfernen, der in Zeile (3) endet). Auf diese Weise stellen wir sicher, dass Thread-t
Aufrufenotify_one
zuvorcv.wait
die neu gesetzte Prädikatvariable überprüfen und fortfahren können, da sie die Sperre abrufen müssten, diet
derzeit gehalten wird, um die Überprüfung durchzuführen. Wir stellen also sicher, dass dercv
Threadt
nach derfoo
Rückgabe nicht darauf zugreift.Zusammenfassend geht es in diesem speziellen Fall nicht wirklich um das Threading, sondern um die Lebensdauer der durch Referenz erfassten Variablen.
cv
wird durch Referenz über einen Thread erfasstt
, daher müssen Sie sicherstellen, dass ercv
für die Dauer der Ausführung des Threads am Leben bleibt. Die anderen hier vorgestellten Beispiele leiden nicht unter diesem Problem, dacondition_variable
undmutex
Objekte im globalen Bereich definiert sind und daher garantiert am Leben bleiben, bis das Programm beendet wird.quelle
@ Michael Burr ist richtig.
condition_variable::notify_one
erfordert keine Sperre für die Variable. Nichts hindert Sie jedoch daran, in dieser Situation eine Sperre zu verwenden, wie das Beispiel zeigt.In dem gegebenen Beispiel wird die Sperre durch die gleichzeitige Verwendung der Variablen motiviert
i
. Da dersignals
Thread die Variable ändert , muss sichergestellt werden, dass während dieser Zeit kein anderer Thread darauf zugreift.Schlösser werden für jede Situation verwendet, die dies erfordert Synchronisation ist. Ich glaube nicht, dass wir dies allgemeiner formulieren können.
quelle
wait
Funktion der Bedingungsvariablen gibt die Sperre innerhalb des Anrufs frei und kehrt erst zurück, nachdem die Sperre wiedererlangt wurde. Nach diesem Zeitpunkt können Sie sicher nach Ihrem Zustand suchen, da Sie beispielsweise die "Leserechte" erworben haben. Wenn es immer noch nicht das ist, worauf Sie warten, kehren Sie zurück zuwait
. Das ist das Muster. Übrigens, dieses Beispiel respektiert es NICHT.In einigen Fällen, wenn der Lebenslauf von anderen Threads belegt (gesperrt) sein kann. Sie müssen die Sperre erhalten und freigeben, bevor Sie _ * () benachrichtigen.
Wenn nicht, wird die Benachrichtigung _ * () möglicherweise überhaupt nicht ausgeführt.
quelle
Fügen Sie einfach diese Antwort hinzu, da ich denke, dass die akzeptierte Antwort irreführend sein könnte. In allen Fällen müssen Sie den Mutex sperren, bevor Sie notify_one () irgendwo aufrufen, damit Ihr Code threadsicher ist. Sie können ihn jedoch möglicherweise erneut entsperren, bevor Sie notify _ * () aufrufen.
Zur Verdeutlichung MÜSSEN Sie die Sperre übernehmen, bevor Sie wait (lk) eingeben, da wait () lk entsperrt und es ein undefiniertes Verhalten wäre, wenn die Sperre nicht gesperrt wäre. Dies ist bei notify_one () nicht der Fall, aber Sie müssen sicherstellen, dass Sie notify _ * () nicht aufrufen, bevor Sie wait () eingeben und diesen Aufruf den Mutex entsperren lassen. Dies kann natürlich nur durch Sperren desselben Mutex erfolgen, bevor Sie notify _ * () aufrufen.
Betrachten Sie beispielsweise den folgenden Fall:
Warnung : Dieser Code enthält einen Fehler.
Die Idee ist folgende: Threads rufen paarweise start () und stop () auf, aber nur solange start () true zurückgibt. Beispielsweise:
Ein (anderer) Thread ruft irgendwann cancel () auf und zerstört nach der Rückkehr von cancel () Objekte, die bei 'Do stuff' benötigt werden. Cancel () soll jedoch nicht zurückkehren, solange sich zwischen start () und stop () Threads befinden. Sobald cancel () die erste Zeile ausgeführt hat, gibt start () immer false zurück, sodass keine neuen Threads in 'Do' eingegeben werden Zeug 'Bereich.
Funktioniert richtig?
Die Argumentation ist wie folgt:
1) Wenn ein Thread die erste Zeile von start () erfolgreich ausführt (und daher true zurückgibt), hat noch kein Thread die erste Zeile von cancel () ausgeführt (wir gehen davon aus, dass die Gesamtzahl der Threads durch die viel kleiner als 1000 ist Weg).
2) Während ein Thread die erste Zeile von start (), aber noch nicht die erste Zeile von stop () erfolgreich ausgeführt hat, ist es unmöglich, dass ein Thread die erste Zeile von cancel () erfolgreich ausführt (beachten Sie, dass nur ein Thread Aufrufe von cancel ()): Der von fetch_sub (1000) zurückgegebene Wert ist größer als 0.
3) Sobald ein Thread die erste Zeile von cancel () ausgeführt hat, gibt die erste Zeile von start () immer false zurück und ein Thread, der start () aufruft, betritt den Bereich 'Do stuff' nicht mehr.
4) Die Anzahl der Aufrufe zum Starten () und Stoppen () ist immer ausgeglichen. Nachdem die erste Zeile von cancel () nicht erfolgreich ausgeführt wurde, gibt es immer einen Moment, in dem ein (letzter) Aufruf zum Stoppen () die Zählung verursacht -1000 erreichen und daher notify_one () aufrufen. Beachten Sie, dass dies nur dann passieren kann, wenn die erste Abbruchzeile dazu geführt hat, dass dieser Thread durchgefallen ist.
Abgesehen von einem Hungerproblem, bei dem so viele Threads start () / stop () aufrufen, dass die Anzahl niemals -1000 erreicht und cancel () niemals zurückkehrt, was man als "unwahrscheinlich und niemals lang anhaltend" akzeptieren könnte, gibt es einen weiteren Fehler:
Es ist möglich, dass sich im Bereich 'Do stuff' ein Thread befindet. Nehmen wir an, er ruft nur stop () auf. In diesem Moment führt ein Thread die erste Zeile von cancel () aus, liest den Wert 1 mit fetch_sub (1000) und fällt durch. Aber bevor der Mutex benötigt wird und / oder der Aufruf zum Warten ausgeführt wird (lk), führt der erste Thread die erste Zeile von stop () aus, liest -999 und ruft cv.notify_one () auf!
Dann wird dieser Aufruf von notify_one () ausgeführt, bevor wir auf die Bedingungsvariable warten ()! Und das Programm würde auf unbestimmte Zeit blockieren.
Aus diesem Grund sollten wir notify_one () erst aufrufen können, wenn wir wait () aufgerufen haben. Beachten Sie, dass die Stärke einer Bedingungsvariablen darin besteht, dass sie den Mutex atomar entsperren kann, prüfen kann, ob ein Aufruf von notify_one () erfolgt ist, und in den Ruhezustand wechseln kann oder nicht. Sie können es nicht täuschen, aber Sie tun müssen die Mutex verschlossen zu halten , wenn Sie Änderungen an Variablen zu machen, die die Bedingung von false auf true ändern könnte und halten sie gesperrt , während notify_one () aufrufen , wegen Rennbedingungen wie hier beschrieben.
In diesem Beispiel gibt es jedoch keine Bedingung. Warum habe ich nicht als Bedingung 'count == -1000' verwendet? Weil das hier überhaupt nicht interessant ist: Sobald -1000 erreicht ist, sind wir sicher, dass kein neuer Thread in den Bereich "Do stuff" gelangen wird. Darüber hinaus können Threads weiterhin start () aufrufen und die Anzahl erhöhen (auf -999 und -998 usw.), aber das interessiert uns nicht. Das einzige, was zählt, ist, dass -1000 erreicht wurde - damit wir sicher wissen, dass es im Bereich "Do stuff" keine Threads mehr gibt. Wir sind sicher, dass dies der Fall ist, wenn notify_one () aufgerufen wird, aber wie kann sichergestellt werden, dass notify_one () nicht aufgerufen wird, bevor cancel () seinen Mutex gesperrt hat? Nur das Sperren von cancel_mutex kurz vor notify_one () hilft natürlich nicht weiter.
Das Problem ist , dass trotz , dass wir nicht auf eine Bedingung warten, gibt es noch ist eine Bedingung, und wir müssen den Mutex sperren
1) bevor diese Bedingung erreicht ist 2) bevor wir notify_one aufrufen.
Der richtige Code lautet daher:
[... gleicher Start () ...]
Natürlich ist dies nur ein Beispiel, aber andere Fälle sind sehr ähnlich. in fast allen Fällen , in denen Sie eine bedingte Variable verwenden , werden Sie brauchen , dass Mutex haben gesperrt (kurz) vor notify_one () aufrufen, oder sonst ist es möglich , dass Sie es vor dem Aufruf von wait () aufrufen.
Beachten Sie, dass ich in diesem Fall den Mutex vor dem Aufruf von notify_one () entsperrt habe, da sonst die (geringe) Wahrscheinlichkeit besteht, dass der Aufruf von notify_one () den Thread aufweckt, der auf die Bedingungsvariable wartet, die dann versucht, den Mutex und zu übernehmen blockieren, bevor wir den Mutex wieder freigeben. Das ist nur etwas langsamer als nötig.
Dieses Beispiel war insofern etwas Besonderes, als die Zeile, die die Bedingung ändert, von demselben Thread ausgeführt wird, der wait () aufruft.
Üblicher ist der Fall, in dem ein Thread einfach darauf wartet, dass eine Bedingung erfüllt wird, und ein anderer Thread die Sperre aufhebt, bevor er die an dieser Bedingung beteiligten Variablen ändert (was dazu führt, dass sie möglicherweise wahr wird). In diesem Fall wird der Mutex unmittelbar vor (und nach) dem Erfüllen der Bedingung gesperrt. In diesem Fall ist es völlig in Ordnung, den Mutex vor dem Aufruf von notify _ * () zu entsperren.
quelle