Condition Variables | Producer Consumer Beispiel



  • Hallo,
    ich bin gerade dabei mir ein wenig nebenläufiges Programmieren in C++ anzuschauen und habe ein Problem mit dem Anwenden von Condition Variables in einem Producer Consumer pattern.
    Ich habe ein einfaches Programm geschrieben mit 3 Threads die jeweils ein paar mal nach kurzem sleep() eine Zahl in eine globale queue pushen (ein Thread nur 1en, ein anderer nur 2en und ein letzter 3en).

    In meinem main thread möchte ich nun immer sobald eine Zahl gepushed wurde benachrichtigt werden um diese verarbeiten zu können.

    Wenn ich mein Programm jedoch ausführe erhalte ich folgende Exception/Fehlermeldung:
    "terminate called after throwing and instance of 'std::system_error'
    what(): Resource deadlock avoided terminate called recursively"

    Kann mir jemand erklären wo mein Fehler liegt?
    Vielen Dank 🙂

    std::queue<int> q;
    std::mutex m;
    std::condition_variable c;
    
    void produceOnes(){
        for(int i = 0; i < 5; i++){
            std::unique_lock<std::mutex> lock(m);
            q.push(1);
            lock.unlock();
    
            c.notify_all();
    
            std::this_thread::sleep_for(std::chrono::milliseconds(300));
        }
    }
    
    void produceTwos(){
        for(int i = 0; i < 5; i++){
            std::unique_lock<std::mutex> lock(m);
            q.push(2);
            lock.unlock();
    
            c.notify_all();
    
            std::this_thread::sleep_for(std::chrono::milliseconds(300));
            }
    }
    
    void produceThree(){
        std::this_thread::sleep_for(std::chrono::seconds(3));
        std::unique_lock<std::mutex> lock(m);
    
        q.push(3);
        lock.unlock();
        c.notify_all();
    }
    
    int main(){
        bool three_found = false;
        std::unique_lock<std::mutex> lock(m);
    
        std::thread t1(produceOnes);
        std::thread t2(produceTwos);
        std::thread t3(produceThree);
    
        while(!three_found){
            c.wait(lock, []{return !q.empty();});
            std::cout << q.front() << std::endl;
            if(q.front() == 3) three_found = true;
            q.pop();
            lock.unlock();
        }
    
        t1.join();
        t2.join();
        t3.join();
    }
    


  • akustiker schrieb:

    Kann mir jemand erklären wo mein Fehler liegt?

    In den Zeilen 8, 21 und 35.

    Du lockst deinen Mutex doppelt(der Konstruktor von unique_lock lockt den Mutex standardmäßig automatisch).



  • Ok, super.
    Wenn ich die Zeilen lösche startet das Programm zwar zumindest schon einmal, aber es printed nur eine 1 und eine 2 und hängt sich dann mit der Fehlermeldung
    "terminate called after throwing an instance of 'std::system_error'
    what(): Operation not permitted"

    Es scheint also noch irgendwo der Wurm drin zu sein...

    EDIT: Auch nachdem ich im 3. Thread das sleep() vor den Konstruktor des locks gelegt habe, sodass dieser nicht schläft während gelocked ist tritt das Problem weiterhin auf. Ich habe den Code oben mal angepasst.



  • Der Debugger sagt doch sicher, woher die Exception kommt und zeigt einen Stacktrace an. Das ist bei der Suche sehr nützlich.



  • Weitere mögliche Ursache ist diese Schleife:

    while(!three_found){
            c.wait(lock, []{return !q.empty();}); // <-- Das hier...
            std::cout << q.front() << std::endl;
            if(q.front() == 3) three_found = true;
            q.pop();
            lock.unlock(); // <-- ...und das hier (siehe unten).
        }
    }
    

    Siehe dazu: http://en.cppreference.com/w/cpp/thread/condition_variable/wait

    und im Besonderen diese Bemerkung:

    Calling this function if lock.mutex() is not locked by the current thread is undefined behavior.

    Finnegan



  • Siehe Finnegan.

    Das unlock() hinter die While-Schleife zu ziehen sollte es richten.



  • Ich glaube ich habe den Fehler gefunden:
    In main() muss der Konstruktor vom unique_lock in die while-schleife gezogen werden, ansonsten lockt da ja auch der Konstruktor direkt.

    Wenn ich dies mache läuft alles soweit durch und die zahlen werden geprinted 🙂

    Allerdings terminiert das Programm irgendwie nie 😕

    Wenn ich ein std::cout ans ende der beiden functions mache werden diese geprinted, etwas nach den beiden joins jedoch nicht...



  • Kannst das ganze ja mal im Debugger ausführen lassen und auf 'programm unterbrechen' oä. klicken. Dann wird dir gezeigt, wo das Programm zu Zeit läuft bzw. wo es hängt.



  • akustiker schrieb:

    Ich glaube ich habe den Fehler gefunden:
    In main() muss der Konstruktor vom unique_lock in die while-schleife gezogen werden, ansonsten lockt da ja auch der Konstruktor direkt.

    Wenn ich dies mache läuft alles soweit durch und die zahlen werden geprinted 🙂

    Ja, funktioniert. Dadurch hast du aber unnötig viel Overhead.
    Besser wäre:

    // ...
        {
            std::unique_lock<std::mutex> lock(m);
            while(!three_found)
            {
                c.wait(lock, []{return !q.empty();});
                std::cout << q.front() << std::endl;
                if(q.front() == 3) three_found = true;
                q.pop();
            }
        }
        // ...
    

    akustiker schrieb:

    Allerdings terminiert das Programm irgendwie nie 😕

    Du hast ja gegenüber dem ursprünglichen Code nun einiges geändert. Wie sieht denn der aktuelle Stand aus?


Anmelden zum Antworten