Ihr Code arbeitet in dem Sinne, dass keine zwei Worker-Threads diesen Code ausführen können:
std::cout << "thread " << id << '\n';
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
cv.notify_one();
gleichzeitig, aber das ist nicht ganz, weil die Nutzung von std::condition_variable
der Arbeiter zu einer Zeit, fädelt ein deblockiert. Tatsächlich haben Sie das Verhalten des ursprünglichen cplusplus.com-Beispiels nicht wirklich geändert. In beiden Programmen ist der kritische Codeabschnitt durch einen gesperrten Mutex geschützt, der garantiert, dass immer nur ein Thread die Sperre halten kann.
Ihre Nutzung von std::condition_variable
zu „serialisiert“ die Ausführung der Threads nicht von selbst die gleichzeitige Ausführung verhindern, weil notify_one()
Aufruf garantiert nicht, dass nur ein Thread von wait()
freigegeben wird. Der Grund dafür ist, dass Threads erlaubt spuriously von wait()
ohne Benachrichtigung zurück an:
Die Funktion freizugeben, wenn durch einen Aufruf von notify_one()
oder einen Anruf notify_all()
, oder spuriously signalisiert.
In gewissem Sinne werden Sie durch den Mutex gerettet. Wenn Sie Ihre Worker-Funktion wie folgt geschrieben haben:
Dieser Code entsperrt den Mutex sobald der Thread entsperrt ist. Dies wäre nicht sicher, da mehrere Threads Ihren kritischen Abschnitt ohne den Schutz des gesperrten Mutex erreichen können.
Also ich denke, Ihr Programm macht was Sie wollen - führt einen Codeabschnitt in vielen Threads ohne Nebenläufigkeit - aber vielleicht nicht aus dem Grund, den Sie erwarten.
Die idiomatische Weise auf einem Zustand der Blockierung ist es, ein Prädikat Test zu definieren, der vergeht, wenn der Faden nicht ablaufen bereit ist aber ansonsten, und es in einer Schleife prüfen:
std::unique_lock<std::mutex> lock(mutex);
while (!predicate)
condition.wait(lock);
Dieses Idiom behandelt fehlerhafte Wake-ups ordnungsgemäß, da der Prädikatstest fehlschlägt und der Thread erneut wait()
aufruft.
Obwohl Ihr Programm diesem sehr ähnlich sieht, tut es das nicht ganz, weil Ihr Prädikat es allen Threads ermöglicht, fortzufahren, nicht nur einem, und das ist nicht das, was Sie angegeben haben. Es stellt sich trotzdem heraus, dass es wegen des gesperrten Mutex funktioniert.
Sie könnten Ihre Fäden einen nach dem anderen, um so durch das Ändern Ihres Prädikat Test freizugeben, dass es nur für den nächsten Faden verläuft, und notify_all()
unter Verwendung:
#include <atomic>
...
std::atomic<int> ready(-1);
void print_id(int id) {
{
std::unique_lock<std::mutex> lck(mtx);
while (ready != id)
cv.wait(lck);
}
std::cout << "thread " << id << '\n';
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
++ready;
cv.notify_all();
}
void go() {
ready = 0;
cv.notify_all();
}
int main()
{
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i<10; ++i)
threads[i] = std::thread(print_id, i);
std::cout << "10 threads ready to race...\n";
go(); // go!
for (auto& th : threads) th.join();
return 0;
}
Hinweis, wie dieses Prädikat Test ready != id
nur geht wenn der Thread fortfahren soll. Ich habe auch eine std::atomic<int>
für ready
verwendet, so dass notification does not require locking the mutex.
Dieser überarbeitete Prädikatcode ist korrekt, aber ein Nachteil ist, dass wir notify_one()
zu notify_all()
ändern müssen, um sicherzustellen, dass wir den nächsten Thread aufwachen. Dies weckt alle Threads auf, nur um alle bis auf einen von ihnen zurück zum Warten zu bringen, was ein wenig Leistung kostet. Eine Möglichkeit, dies zu optimieren, wäre das Erzeugen von N condition_variable
Instanzen (z. B. in einem Array), und jeder Thread wartet auf seine eigene condition_variable
Instanz.
Mit Bedingungsvariablen kann nicht ermittelt werden, welcher der beiden Threads beim Aufruf von 'notify_one' geweckt wird. Es gibt auch keine Möglichkeit, die Verwendung von Bedingungsvariablen zu erzwingen. Sie können auch einfach einen einfachen Mutex und eine Variable verwenden, die sagt, welcher Thread ausgeführt werden soll, den die Threads abfragen (mit dem Mutex und einem zufälligen kurzen Schlaf). –