why do I need std::condition_variable?
I found that std::condition_variable
is very difficult to use due to spurious wakeups. So sometimes I need to set a flags such as:
atomic<bool> is_ready;
I set is_ready
to true
before I call notify (notify_one()
or notify_all()
), and then I wait:
some_condition_variable.wait(some_unique_lock, [&is_ready]{
return bool(is_ready);
});
Is there any reason that I shouldn't just do this: (Edit: Ok, this is really a bad idea.)
while(!is_ready) {
this_thread::wait_for(some_duration); //Edit: changed from this_thread::yield();
}
And if condition_variable
had chosen a waiting duration (I don't know whether this is true or not), I prefer choose it myself.
You can code this either way:
- Using atomics and a polling loop.
- 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.