Muss ich eine Sperre erwerben, bevor ich condition_variable.notify_one () aufrufe?

86

Ich bin etwas verwirrt über die Verwendung von std::condition_variable. Ich verstehe, dass ich vor dem Anruf ein unique_lockon erstellen muss . Was ich nicht finden kann ist, ob ich vor dem Anruf oder auch eine eindeutige Sperre erwerben soll .mutexcondition_variable.wait()notify_one()notify_all()

Beispiele auf cppreference.com sind widersprüchlich. Auf der Seite notify_one finden Sie beispielsweise folgendes Beispiel:

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    cv.notify_one();

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) {
        lk.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
        std::cerr << "Notifying again...\n";
        cv.notify_one();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); t2.join();
}

Hier wird das Schloss nicht für das erste notify_one(), sondern für das zweite erworben notify_one(). Wenn ich auf anderen Seiten mit Beispielen nachschaue, sehe ich andere Dinge, meistens ohne das Schloss zu erwerben.

  • Kann ich mich entscheiden, den Mutex vor dem Anruf zu sperren notify_one(), und warum sollte ich ihn sperren?
  • Warum gibt es im angegebenen Beispiel keine Sperre für den ersten notify_one(), aber für nachfolgende Aufrufe? Ist dieses Beispiel falsch oder gibt es eine Begründung?
Peter Smit
quelle

Antworten:

74

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 whileSchleife 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_variableist 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 iund 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 iund wenn der Verbraucher eincheckt und wartet i, besteht keine Möglichkeit i, zwischen dem Zeitpunkt, zu dem der Verbraucher prüft, iund 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 i0 ist, ruft der Verbraucher auf cv.wait(), dann iist 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 whileSchleife 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 ibeim 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 doneVariable 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 iund 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->counteres sich um einen std::atomicTyp handelt. Die von bereitgestellte Atomizität std::atomicerstreckt 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->counterdas 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.

Michael Burr
quelle
2
Dies hat tiefere Auswirkungen: domaigne.com/blog/computing/… Insbesondere sollte das von Ihnen erwähnte Pthread-Problem entweder durch eine neuere Version oder eine Version mit den richtigen Flags gelöst werden. (um die wait morphingOptimierung 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.
v.oddou
5
@ Michael: Nach meinem Verständnis muss der Verbraucher irgendwann anrufen 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 ...
Cookie
Beispiel: Im obigen Code befindet sich der Verbraucher, std::cout << "Waiting... \n";während der Produzent dies tut cv.notify_one();. Dann geht der Weckruf verloren ... Oder fehlt mir hier etwas?
Cookie
@Plätzchen. Ja, dort gibt es eine Rennbedingung. Siehe stackoverflow.com/questions/20982270/…
eh9
@ eh9: Verdammt, ich habe gerade die Ursache für einen Fehler gefunden, der meinen Code dank Ihres Kommentars von Zeit zu Zeit einfriert. Es war genau auf diesen Fall des Rennzustands zurückzuführen. Das Entsperren des Mutex nach der Benachrichtigung hat das Problem vollständig gelöst ... Vielen Dank!
Galinette
10

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:

void push(const T& item)
{
  std::unique_lock<std::mutex> mlock(mutex_);
  queue_.push(item);
  mlock.unlock();     // unlock before notificiation to minimize mutex contention
  cond_.notify_one(); // notify one waiting thread
}

Das Entsperren des Mutex wird durch ein Beispiel in der Boost-Dokumentation unterstützt :

void prepare_data_for_processing()
{
    retrieve_data();
    prepare_data();
    {
        boost::lock_guard<boost::mutex> lock(mut);
        data_ready=true;
    }
    cond.notify_one();
}

Problem

Dies führte jedoch zu folgendem unberechenbarem Verhalten:

  • solange notify_one()noch nicht aufgerufen wurde cond_.wait()kann noch über unterbrochen werdenboost::thread::interrupt()
  • einmal notify_one()wurde zum ersten Mal cond_.wait()Deadlocks genannt; das Warten nicht durch beendet werden boost::thread::interrupt()oder boost::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 der notify_one()aufgerufene Mutex beim Verlassen des Gültigkeitsbereichs sofort danach entsperrt wird:

void push(const T& item)
{
  std::lock_guard<std::mutex> mlock(mutex_);
  queue_.push(item);
  cond_.notify_one(); // notify one waiting thread
}

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.

Matthäus Brandl
quelle
Haben Sie dieses Problem Boost.Thread gemeldet? Ich kann dort keine ähnliche Aufgabe finden svn.boost.org/trac/boost/…
magras
@magras Leider habe ich nicht, keine Ahnung, warum ich das nicht berücksichtigt habe. Und leider gelingt es mir nicht, diesen Fehler mit der genannten Warteschlange zu reproduzieren.
Matthäus Brandl
Ich bin mir nicht sicher, wie früh das Aufwachen zu einem Deadlock führen kann. Insbesondere wenn Sie cond_.wait () in pop () verlassen, nachdem push () den Warteschlangenmutex freigegeben hat, aber bevor notify_one () aufgerufen wird, sollte Pop () die Warteschlange nicht leer sehen und den neuen Eintrag anstelle von verbrauchen warten. Wenn Sie cond_.wait () verlassen, während push () die Warteschlange aktualisiert, sollte die Sperre durch push () gehalten werden. Daher sollte pop () das Warten auf die Freigabe der Sperre blockieren. Alle anderen frühen Weckvorgänge halten die Sperre und verhindern, dass push () die Warteschlange ändert, bevor pop () das nächste wait () aufruft. Was habe ich verpasst?
Kevin
4

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 Schloss condition_variablezerstört wird, bevor notify_one()es aufgerufen wird. Betrachten Sie das folgende Beispiel:

thread t;

void foo() {
    std::mutex m;
    std::condition_variable cv;
    bool done = false;

    t = std::thread([&]() {
        {
            std::lock_guard<std::mutex> l(m);  // (1)
            done = true;  // (2)
        }  // (3)
        cv.notify_one();  // (4)
    });  // (5)

    std::unique_lock<std::mutex> lock(m);  // (6)
    cv.wait(lock, [&done]() { return done; });  // (7)
}

void main() {
    foo();  // (8)
    t.join();  // (9)
}

Angenommen, es gibt einen Kontextwechsel zum neu erstellten Thread, tnachdem wir ihn erstellt haben, aber bevor wir auf die Bedingungsvariable warten (irgendwo zwischen (5) und (6)). Der Thread terfasst die Sperre (1), setzt die Prädikatvariable (2) und gibt dann die Sperre (3) frei. Angenommen, es gibt an dieser Stelle einen weiteren Kontextwechsel, bevor notify_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ück trueund es besteht kein Grund zum Warten. Daher wird die Sperre aufgehoben und fortgesetzt. fooreturn (8) und die Variablen in ihrem Bereich (einschließlich cv) werden zerstört. Bevor der Thread tdem 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 ​​Punkt cvist bereits zerstört!

Die mögliche Lösung in diesem Fall besteht darin, die Sperre beim Aufrufen notify_onegedrückt zu halten (dh den Bereich zu entfernen, der in Zeile (3) endet). Auf diese Weise stellen wir sicher, dass Thread- tAufrufe notify_onezuvor cv.waitdie neu gesetzte Prädikatvariable überprüfen und fortfahren können, da sie die Sperre abrufen müssten, die t derzeit gehalten wird, um die Überprüfung durchzuführen. Wir stellen also sicher, dass der cvThread tnach der fooRü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. cvwird durch Referenz über einen Thread erfasst t, daher müssen Sie sicherstellen, dass er cvfür die Dauer der Ausführung des Threads am Leben bleibt. Die anderen hier vorgestellten Beispiele leiden nicht unter diesem Problem, da condition_variableund mutexObjekte im globalen Bereich definiert sind und daher garantiert am Leben bleiben, bis das Programm beendet wird.

cantunca
quelle
1

@ Michael Burr ist richtig. condition_variable::notify_oneerfordert 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 der signalsThread 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.

didierc
quelle
Natürlich, aber obendrein müssen sie auch in Verbindung mit Bedingungsvariablen verwendet werden, damit das gesamte Muster tatsächlich funktioniert. Insbesondere die waitFunktion 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 zu wait. Das ist das Muster. Übrigens, dieses Beispiel respektiert es NICHT.
v.oddou
1

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.

Fan Jing
quelle
1

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.

Carlo Wood
quelle