Search code examples
c++multithreadingcondition-variable

std::condition_variable predictable spurious wake-ups?


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.


Solution

  • 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.