Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why do I need std::condition_variable?

People also ask

What is the use of Condition_variable?

The condition_variable class is a synchronization primitive that can be used to block a thread, or multiple threads at the same time, until another thread both modifies a shared variable (the condition), and notifies the condition_variable .

Why do condition variables need mutex?

Condition variables are associated with a mutex because it is the only way it can avoid the race that it is designed to avoid.

How conditional variables work C++?

A condition variable is an object able to block the calling thread until notified to resume. It uses a unique_lock (over a mutex ) to lock the thread when one of its wait functions is called.

Does unique_lock automatically unlock?

The std::scoped_lock and std::unique_lock objects automate some aspects of locking, because they are capable of automatically unlocking.


You can code this either way:

  1. Using atomics and a polling loop.
  2. Using a condition_variable.

I've coded it both ways for you below. On my system I can monitor in real time how much cpu any given process is using.

First with the polling loop:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

std::atomic<bool> is_ready(false);

void
test()
{
    std::this_thread::sleep_for(std::chrono::seconds(30));
    is_ready.store(true);
}

int
main()
{
    std::thread t(test);
    while (!is_ready.load())
        std::this_thread::yield();
    t.join();
}

For me this takes 30 seconds to execute, and while executing the process takes about 99.6% of a cpu.

Alternatively with a condition_variable:

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

bool is_ready(false);
std::mutex m;
std::condition_variable cv;

void
test()
{
    std::this_thread::sleep_for(std::chrono::seconds(30));
    std::unique_lock<std::mutex> lk(m);
    is_ready = true;
    cv.notify_one();
}

int
main()
{
    std::thread t(test);
    std::unique_lock<std::mutex> lk(m);
    while (!is_ready)
    {
        cv.wait(lk);
        if (!is_ready)
            std::cout << "Spurious wake up!\n";
    }
    t.join();
}

This has the exact same behavior except that during the 30 second execution, the process is taking 0.0% cpu. If you're writing an app that might execute on a battery powered device, the latter is nearly infinitely easier on the battery.

Now admittedly, if you had a very poor implementation of std::condition_variable, it could have the same inefficiency as the polling loop. However in practice such a vendor ought to go out of business fairly quickly.

Update

For grins I augmented my condition_variable wait loop with a spurious wakeup detector. I ran it again, and it did not print out anything. Not one spurious wakeup. That is of course not guaranteed. But it does demonstrate what a quality implementation can achieve.


The purpose of std::condition_variable is to wait for some condition to become true. It is not designed to be just a receiver of a notify. You might use it, for example, when a consumer thread needs to wait for a queue to become non-empty.

T get_from_queue() {
   std::unique_lock l(the_mutex);
   while (the_queue.empty()) {
     the_condition_variable.wait(l);
   }
   // the above loop is _exactly_ equivalent to the_condition_variable.wait(l, [&the_queue](){ return !the_queue.empty(); }
   // now we have the mutex and the invariant (that the_queue be non-empty) is true
   T retval = the_queue.top();
   the_queue.pop();
   return retval;
}

put_in_queue(T& v) {
  std::unique_lock l(the_mutex);
  the_queue.push(v);
  the_condition_variable.notify_one();  // the queue is non-empty now, so wake up one of the blocked consumers (if there is one) so they can retest.
}

The consumer (get_from_queue) is not waiting for the condition variable, they are waiting for the condition the_queue.empty(). The condition variable gives you the way to put them to sleep while they are waiting, simultaneously releasing the mutex and doing so in a way that avoids race conditions where you miss wake ups.

The condition you are waiting on should be protected by a mutex (the one you release when you wait on the condition variable.) This means that the condition rarely (if ever) needs to be an atomic. You are always accessing it from within a mutex.