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:
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999) // Reached -1000 ?
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
// Failure.
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0) // Reached -1000?
return;
// Wait till count reached -1000.
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
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:
if (start())
{
// Do stuff
stop();
}
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:
void stop()
{
if (count.fetch_sub(1) == -999) // Reached -1000 ?
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... gleicher Start () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
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.
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.