Search code examples
c++multithreadingconcurrencycondition-variable

How to use a std::condition_variable correctly?


I'm confused about conditions_variables and how to use them (safely). In my application I've a class that makes a gui-thread but while the gui is constructed by the gui-thread, the main thread needs to wait.

The situation is the same as for the function below. The main thread makes a mutex, lock and condition_variable. It then makes the thread. While this worker thread has not passed a certain point (here printing the numbers), the main thread is not allowed to continue (i.e. has to wait for all numbers being printed).

How do I use condition_variables correctly in this context? Also, I've read that spontaneous wake-ups are an issue. How can I handle them?

    int main()
    {
        std::mutex mtx;
        std::unique_lock<std::mutex> lck(mtx);
        std::condition_variable convar;

        auto worker = std::thread([&]{
            /* Do some work. Main-thread can not continue. */
            for(int i=0; i<100; ++i) std::cout<<i<<" ";
            convar.notify_all(); // let main thread continue
            std::cout<<"\nworker done"<<std::endl;       
        });


        // The main thread can do some work but then must wait until the worker has done it's calculations.
        /* do some stuff */
        convar.wait(lck);
        std::cout<<"\nmain can continue"<<std::endl; // allowed before worker is entirely finished
        worker.join();
    }

Solution

  • Typically you'd have some observable shared state on whose change you block:

    bool done = false;
    std::mutex done_mx;
    std::condition_variable done_cv;
    
    {
      std::unique_lock<std::mutex> lock(done_mx);
    
      std::thread worker([&]() {
        // ...
        std::lock_guard<std::mutex> lock(done_mx);
        done = true;
        done_cv.notify_one();
      });
    
      while (true) { done_cv.wait(lock); if (done) break; }
    
      // ready, do other work
    
      worker.join();
    }
    

    Note that you wait in a loop until the actual condition is met. Note also that access to the actual shared state (done) is serialized via the mutex done_mx, which is locked whenever done is accessed.

    There's a helper member function that performs the condition check for you so you don't need the loop:

    done_cv.wait(lock, [&]() { return done; });