I ran into this interesting behavior of spurious wake-ups. Consider this simple demo code:
#include <iostream>
#include <chrono>
#include <thread>
#include <condition_variable>
#include <mutex>
using namespace std; // I know
using namespace std::chrono;
using namespace std::chrono_literals;
mutex mtx; // used for cv and synchronized access to counter
condition_variable cv;
int counter = 0; // (1)
bool keep_running = true; // flag for signaling an exit condition
int main()
{
// thread that decrements counter every time it is woken up and the counter is > 0
thread t([&] {
while (keep_running)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&] {
cout << "Woken up" << endl;
return !keep_running || counter > 0;
});
if (!keep_running) { // check for exit condition
break;
}
--counter;
}
});
this_thread::sleep_for(1s); // some work
unique_lock<mutex> lock(mtx);
counter = 5; // set the counter
cout << "Notifying" << endl;
lock.unlock();
cv.notify_one(); // wake the thread up
this_thread::sleep_for(1s); // some more work
cout << "Exiting" << endl;
lock.lock();
keep_running = false; // ask the thread to exit
lock.unlock();
cv.notify_one(); // wake up one last time
t.join(); // join and exit
cout << "Counter: " << counter << endl;
}
Compiling with g++ cv_test.cpp -o cv_test -pthread
and executing produces the following output:
Woken up
Notifying
Woken up
Woken up
Woken up
Woken up
Woken up
Woken up
Exiting
Woken up
Counter: 0
Notice that I call notify_one
only once, but the thread is woken up continuously until the predicate returns false. No matter what the counter is initialized to, the thread is woken up until it goes to 0 (which is the predicate).
Even at the beginning of the execution, the thread is woken up once as if to "check" that the predicate returns false. Consequently, if I initialize the counter to a positive value: int counter = 3; // (1)
, the spurious wake-ups seem to "ensure" the predicate returns false even before the first notify_one
is called.
My question is, is this really a feature and can it be relied upon? Is there any documentation on this behavior?
PS. I know this worker thread can be fixed by a simple check for the counter (read: work queue length) before waiting on the condition_variable, but this kind of predictable behavior of the spurious wake-ups intrigued me.
I realized immediately after I posted this question that this overload of condition_variable::wait
(as described here) is equivalent to:
while (!pred()) {
wait(lock);
}
I was imagining it being equivalent to a do while
instead. So really there are no spurious wake-ups here. Just that it wasn't waiting at all until the predicate returned false.