mercredi 15 février 2023

C++ std::condition_variable usage to synchronize two threads

I'm trying to understand how the std::condition_variable works and fail to understand.

I have composed the below program, that its objective is to "guarentee" that thread1 will print "foo", followed by thread2 that will print "bar" and then again thread1 "foo" and eventually thread2 will print "bar". When I run the program, sometimes the objective is achived, i.e. - it prints "foobarfoobar" but sometimes it does not - and it prints "foobarbarfoo".

I struggelling to understand what am I missing in my implementation here. Note, if I run the program with n=1 it always outputs the required output.

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

using namespace std;

mutex m;
condition_variable cv;
bool ready = false;
bool processed = false;
int numOfIterations = 2;

void worker_thread2()
{
    cout << "worker_thread2 - start" << endl;
    for (int i = 0; i < numOfIterations; ++i)
    {
        cout << "worker_thread2 - iteration:" << i << endl;
        // Wait until thread1 sends data
        unique_lock lk(m);
        cv.wait(lk, []{return ready;});
        // after the wait, we own the lock.
        cout << "bar" << endl;
 
        // Send data back to thread1
        processed = true;

        // Manual unlocking is done before notifying, to avoid waking up
        // the waiting thread only to block again (see notify_one for details)
        lk.unlock();
        cv.notify_one();
    }
    cout << "worker_thread2 - end" << endl;
}

void worker_thread1()
{
    cout << "worker_thread1 - start" << endl;
    for (int i = 0; i < numOfIterations; ++i)
    {
        cout << "worker_thread1 - iteration:" << i << endl;
        {
            lock_guard lk(m);
            cout << "foo" << endl;
            ready = true;
        }
        cv.notify_one();
        // wait for the worker2 to finish
        {
            cout << "worker_thread1 BEFORE unique_lock lk(m)" << endl;
            unique_lock lk(m);
            cv.wait(lk, []{return processed;});
        }
    }
    cout << "worker_thread1 - end" << endl;
}

int main()
{
    cout << "main - about to start the two worker threads" << endl;
    thread worker1(worker_thread1);
    thread worker2(worker_thread2);
    cout << "main - joining the two worker threads" << endl;
    worker1.join();
    worker2.join();
    cout << "main - end" << endl;
    return 0;
}

I guess that I'm not using the ready & processed properly, but whenever I try to change it I get to a deadlock.

Aucun commentaire:

Enregistrer un commentaire