2016-07-11 6 views
0

Vor diesem Beispiel Code entnommen und von cplusplus.comVerwendung Bedingungsvariable Fäden einer nach dem anderen

#include "stdafx.h" 
// condition_variable example 
#include <iostream>   // std::cout 
#include <thread>    // std::thread 
#include <mutex>    // std::mutex, std::unique_lock 
#include <condition_variable> // std::condition_variable 

std::mutex mtx; 
std::condition_variable cv; 
bool ready = false; 

void print_id(int id) { 
    std::unique_lock<std::mutex> lck(mtx); 
    while (!ready) 
    { 
     std::cout << "waiting for unlock"; 
     cv.wait(lck); 
    } 

    std::cout << "thread " << id << '\n'; 
    std::this_thread::sleep_for(std::chrono::milliseconds(2000)); 
    cv.notify_one(); 
} 

void go() { 
    std::unique_lock<std::mutex> lck(mtx); 
    ready = true; 
    cv.notify_one(); 
} 

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; 
} 

modifiziert auszulösen Enthält diese jeden Thread blockiert, bis ein ordnungsgemäß durch die notify_one() aufrufen, aufgeweckt? oder entschlüsselt das im Wesentlichen alle Threads?

Ich versuche, alle Threads gleichzeitig zu starten und dann zu blockieren, bis der vorherige beendet ist. Die Reihenfolge ist nicht wichtig, aber ich kann nicht zulassen, dass sie das Fleisch des Codes gleichzeitig ausführen.

+1

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). –

Antwort

2

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.

+0

Können Sie vorschlagen, wie Sie meine Haupt/print_id Funktion ändern, um entsprechend zu sperren und zu warten, so dass die Threads: 1. In Reihenfolge laufen 2. Sperren und warten auf sichere Weise 3. Führen Sie nacheinander – Jason

+0

Ihr Programm bereits tut (2) und (3). Ihre Frage sagt "Bestellung ist nicht wichtig" - ändern Sie diese Anforderung? – rhashimoto

+0

Basierend auf Ihren ursprünglichen Kommentaren, habe ich festgestellt, dass, während mein Programm tatsächlich blockierte, es nicht so passend war. Ich habe nach dem gesucht, was du als den "besseren Weg" angesehen hast. Während Bestellung ist keine Voraussetzung, es ist ein schönes zu haben. Wenn es also möglich ist, in Ordnung zu sein, wäre das großartig. – Jason

0

Nach cppreference:

void notify_one();

Wenn alle Threads auf warten * this, notify_one Aufruf deblockiert einer der wartenden Threads.

Also ist es nur ein Thread entsperren und Sie können nicht sagen, welche. Um alle Themen Sie void notify_all();

verwenden müssen zu entsperren Wenn Sie alle Threads auszuführen sind versuchen, wenn Sie notify_all() in go() und dann notify_one() zum sequentiellen Durchlauf in print_id(int)

Denken Sie daran, dass auch bei Verwendung von notify_all() verwenden sollten Alle Threads versuchen, lck zu sperren, aber nur einer wird erfolgreich sein.

Verwandte Themen