2

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();
    }
dani
  • 2,917
  • 3
  • 19
  • 43
  • Why "correctly"? Do you expect that by default people would explain how to use something *incorrectly*?! And what does "safely" mean? – Kerrek SB May 10 '17 at 16:39

1 Answers1

4

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; });
Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • Why do you need the lock_guard? – dani May 10 '17 at 16:59
  • @dani, to automatically unlock the mutex upon leaving the scope. – SergeyA May 10 '17 at 17:17
  • 1
    I just wanted to say, never use a lambda with automatic reference capture as a thread function. So. Many. Bugs. – Zan Lynx May 10 '17 at 17:18
  • @ZanLynx: What's the bug? – Kerrek SB May 10 '17 at 17:21
  • 1
    @KerrekSB: I don't think that you have one here, but I've seen so many. For example, adding a use of a variable that isn't protected by a mutex. Using a reference to a stack variable in a function that exited. – Zan Lynx May 10 '17 at 17:23
  • @KerrekSB the `while` loop with `done_cv.wait` is invalid – LWimsey May 10 '17 at 19:03
  • @KerrekSB is there a bug in the scope of the `lock_guard`? According to the last answer here: http://stackoverflow.com/questions/11043049/c11-stdcondition-variable-can-we-pass-our-lock-directly-to-the-notified-thr?rq=1 and the documentation: http://en.cppreference.com/w/cpp/thread/condition_variable the lock must go out of scope to unlock the mutex before notifying. – dani May 12 '17 at 07:06
  • @dani: I think you can notify the CV whenever you like, either while you still hold the lock or after. The notification is just queued up; it can of course only be delivered once the mutex is eventually unlocked. – Kerrek SB May 12 '17 at 09:35
  • @dani It might actually be a bug to unlock the mutex before calling notify_one. See for example this answer: https://stackoverflow.com/a/52671238/1487069 – Carlo Wood Apr 09 '21 at 11:10