Concurrency[conc]

Condition variable in C++: std::condition_variable explained

Section
Concurrency [conc]
Standard
C++17
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
8 min read
In this answer8 clauses
  1. 1How does condition variable wait work?
  2. 2Why does wait need a predicate?
  3. 3Why std::unique_lock and not std::lock_guard?
  4. 4How do you build a producer-consumer queue?
  5. 5notify_one or notify_all?
  6. 6How do you wait with a timeout?
  7. 7How do you cancel a wait in C++20?
  8. 8A condition variable checklist

Abstract

A condition variable in C++, std::condition_variable from <condition_variable>, lets a thread sleep until another thread changes shared data and tells it so. The waiter locks a std::mutex with a std::unique_lock and calls cv.wait(lock, pred), which returns once pred() is true; the notifier changes the data under the same mutex, then calls notify_one() or notify_all(). Always pass the predicate: it handles spurious wakeups and notifications sent before the wait began.

Two hatched bars run left to right, one above the other. The upper one breaks into a hollow, dotted stretch, then is hatched again where a blue arrow from the lower bar arrives.
Figure 1 — One thread sleeps until the other signals.

A condition variable lets a thread sleep until another thread changes some shared data and signals it; a mutex guards that data. In C++ it is std::condition_variable, from <condition_variable>: the waiting thread locks the mutex with a std::unique_lock and calls wait with a condition to check; the other thread changes the data under the same mutex and calls notify_one or notify_all.

Here a worker std::thread waits for main to set a ready flag:

[Example 1:
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

std::mutex m;
std::condition_variable cv;
bool ready = false;

void worker() {
    std::unique_lock lock(m);
    cv.wait(lock, [] { return ready; });  // sleeps until ready is true
    std::cout << "worker: ready is true, going on\n";
}

int main() {
    std::thread t(worker);
    std::cout << "main: setting ready\n";
    {
        std::lock_guard lock(m);
        ready = true;  // change the condition under the mutex
    }
    cv.notify_one();  // then wake the waiting thread
    t.join();
}

— end example]

It printed:

[Example 2:
main: setting ready
worker: ready is true, going on

— end example]

The answer is written for C++17 (Since When’s C++17 table) only so the samples can write std::unique_lock lock(m); before C++17, write std::unique_lock<std::mutex> lock(m), and the same for std::lock_guard. std::condition_variable itself dates from C++11. The samples were built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++), and all four printed the same, in the same order on every run: the worker prints only after it sees ready under the mutex.

Three things always go together: a std::mutex, a condition in the data it guards (ready), and the condition variable the waiting thread sleeps on. Together they replace a loop that checks a flag over and over, busy waiting, often written with a std::atomic flag: the thread sleeps until it is told to look again.

How does condition variable wait work?

cv.wait(lock, pred) checks the condition before it sleeps. While pred() is false, it releases the mutex and blocks in one atomic step, so a change made under the mutex cannot fall between the check and the sleep; when woken, it locks the mutex again and calls pred() again. It returns only when pred() is true, holding the mutex. In the first sample pred is the lambda [] { return ready; }.

The standard defines this form as “Equivalent to while (!pred()) wait(lock);” ([thread.condition.condvar]). Every thread waiting on one condition variable must use the same mutex, and calling wait with a lock that does not own it is undefined behavior.

Why does wait need a predicate?

Because wait can return when nothing changed, and can sleep through a change that came first. The predicate fixes both by checking the data, not the signal.

A spurious wakeup is a wait that returns though no thread notified it, and the standard allows one (“or spuriously”). In one sample three threads waited with wait_for(lock, 1s) and no predicate, and main called notify_one() once. On the two Linux builds exactly one wait returned std::cv_status::no_timeout (woken, not timed out), every run; on Windows (both compilers, Microsoft’s library), in some runs a second waiter that nobody notified also returned it when its second ran out. So no_timeout from a wait without a predicate does not prove the condition changed.

A lost wakeup is the opposite case. A notification is not stored, so a notify_one() sent while no thread waits wakes nobody. Here main sets ready and notifies before either worker starts; the worker without a predicate uses wait_for with 200 ms in place of a bare wait, so the program ends:

[Example 3:
std::unique_lock lock(m);
// a bare cv.wait(lock) would sleep forever here
auto s = cv.wait_for(lock, std::chrono::milliseconds(200));
if (s == std::cv_status::timeout)
    std::cout << "bare wait: timed out, notification lost\n";

— end example]

All four printed bare wait: timed out, notification lost. A bare wait(lock) would sleep until a later notification or a spurious wakeup, and with neither, forever. The worker that waited with the predicate printed with a predicate: ready is true, going on at once. The notification was lost for it too; it went on because wait checked ready first, found it true and never slept.

Two pairs of timelines. In both, the notifier sets ready to true and calls notify_one before anyone waits; a grey arrow marked lost stops short. Then a bare wait(lock) sleeps forever, while wait(lock, pred) finds ready true and goes on.
Figure 2 — A notification that arrives before the wait is lost; a predicate makes the waiter check the condition first.

Why std::unique_lock and not std::lock_guard?

Because wait must release the mutex while it sleeps and take it again when it wakes. A std::unique_lock can be released and relocked; a std::lock_guard cannot: it holds its mutex until its scope ends. std::condition_variable works only with a std::unique_lock<std::mutex>, and a wait given a std::lock_guard compiled on none of the four. The notifying side only locks around the change, so a std::lock_guard or std::scoped_lock is fine there, as in the first sample.

How do you build a producer-consumer queue?

The producer pushes jobs into a std::queue under the mutex and calls notify_one() for each; the consumer waits until the queue is not empty or the producer is done, pops one job and processes it outside the lock. Here main is the producer:

[Example 4:
std::mutex m;
std::condition_variable cv;
std::queue<int> jobs;
bool done = false;

void consumer() {
    for (;;) {
        std::unique_lock lock(m);
        cv.wait(lock, [] { return !jobs.empty() || done; });
        if (jobs.empty())  // done, and nothing left to do
            break;
        int job = jobs.front();
        jobs.pop();
        lock.unlock();  // work outside the lock
        std::cout << "processing job " << job << '\n';
    }
    std::cout << "consumer done\n";
}

int main() {
    std::thread worker(consumer);
    for (int job = 1; job <= 5; ++job) {
        {
            std::lock_guard lock(m);
            jobs.push(job);
        }
        cv.notify_one();
    }
    {
        std::lock_guard lock(m);
        done = true;
    }
    cv.notify_all();
    worker.join();
}

— end example]

It printed:

[Example 5:
processing job 1
processing job 2
processing job 3
processing job 4
processing job 5
consumer done

— end example]

What makes it end cleanly:

  • done is set under the same mutex as the jobs, and notify_all() wakes every consumer waiting for it.
  • The consumer leaves only when the queue is empty, so it finishes the jobs already queued.
  • lock.unlock() comes before the work, so the producer can push the next job meanwhile.

notify_one or notify_all?

notify_one() wakes one waiting thread: use it when any one waiter can handle the change, such as one new job. notify_all() wakes them all: use it when the change matters to every waiter, such as a shutdown flag.

Change the condition while holding the mutex, even when it is atomic, so the change cannot fall between a waiter’s check and its sleep. The notify call itself need not hold it: cppreference’s notify_one page says the notifying thread “does not need to hold the lock” and calls holding it “a pessimization”.

How do you wait with a timeout?

cv.wait_for(lock, duration, pred) waits until pred() is true or the duration has passed, then returns the predicate’s last value, so false means the time ran out with the condition still false. cv.wait_until(lock, time_point, pred) does the same up to a point in time. Here nobody sets ready:

[Example 6:
bool got = cv.wait_for(lock, std::chrono::milliseconds(100),
                       [] { return ready; });
if (got)
    std::cout << "ready\n";
else
    std::cout << "timed out: ready is still false\n";

— end example]

All four printed timed out: ready is still false, and wait_until with a deadline 100 ms ahead returned false too. Without a predicate, wait_for returns std::cv_status::timeout or std::cv_status::no_timeout, and the Windows result above shows that no_timeout does not prove a notification.

How do you cancel a wait in C++20?

Give wait a std::stop_token. Since C++20, std::condition_variable_any has wait overloads that take one, so a std::jthread asked to stop wakes up with no done flag of your own; std::condition_variable has none. The queue again, with cv declared as a std::condition_variable_any and the consumer taking its std::jthread’s stop token:

[Example 7:
void consumer(std::stop_token stop) {  // C++20: std::stop_token
    std::unique_lock lock(m);
    while (cv.wait(lock, stop,  // C++20: wait with a stop_token
                   [] { return !jobs.empty(); })) {
        int job = jobs.front();
        jobs.pop();
        lock.unlock();
        std::cout << "processing job " << job << '\n';
        lock.lock();
    }
    std::cout << "consumer: stop requested, queue empty\n";
}

— end example]

main queues three jobs, then calls worker.request_stop(). All four printed processing job 1 to processing job 3, then consumer: stop requested, queue empty. The wait returns pred() even after a stop request, so the consumer drains the queue first and gets false only when a stop was requested and the queue is empty (cppreference’s condition_variable_any::wait).

First releases with the token wait, from Since When’s C++20 table: GCC 10.1, Clang 20 with libc++ and Visual Studio 2019 16.9.

A condition variable checklist

  • One mutex, one condition in the data it guards, one condition variable.
  • Change the condition under the mutex; notify after.
  • Wait with a std::unique_lock and a predicate, never a bare wait.
  • notify_one() for one new piece of work, notify_all() for a change every waiter needs, such as shutdown.
  • A timed wait: act on the bool it returns.
  • Do the work outside the lock.

More on threads and shared data: Concurrency.

Questions

What is a condition variable?

A condition variable is a synchronization object that lets a thread sleep until another thread changes some shared data and signals it. It is used together with a mutex, which guards that data. In C++ it is std::condition_variable, from <condition_variable>, since C++11; POSIX threads and other languages offer the same idea under their own names.

What is a spurious wakeup?

A spurious wakeup is a wait that returns although no thread called notify_one() or notify_all(); the C++ standard allows it. Waiting with a predicate, as in cv.wait(lock, [] { return ready; }), handles it: the predicate is checked again after every wakeup, and wait goes back to sleep while it is false.

What is the difference between a condition variable and a mutex?

A mutex protects shared data: one thread at a time may hold it. A condition variable lets a thread wait until that data reaches a state, such as a queue that is no longer empty, without checking it in a loop. The two work together: the condition is read and changed under the mutex, and wait releases the mutex while the thread sleeps.

Semaphore vs condition variable: which should I use?

A semaphore (std::counting_semaphore, C++20, in <semaphore>) keeps a count: release() adds one and acquire() takes one, blocking at zero, so a release is remembered and no mutex or predicate is needed. A condition variable keeps no count: it waits for any condition you can test under a mutex, and a notification sent while nobody waits is lost. Use a semaphore to let a fixed number of threads through, a condition variable to wait for a state of shared data.

Do I have to hold the lock when calling notify_one?

No. Change the shared data while holding the mutex, then call notify_one() or notify_all(), before or after releasing it; cppreference calls notifying under the lock a pessimization. Keep holding it for the call when the waiting thread may destroy the condition variable as soon as it sees the change; otherwise the notify can run on a destroyed object.

What is std::condition_variable_any?

std::condition_variable_any is a condition variable that works with any lock type that has lock() and unlock(). std::condition_variable accepts only a std::unique_lock of std::mutex, which cppreference says allows maximal efficiency on some platforms. Since C++20, std::condition_variable_any also has wait overloads that take a std::stop_token, so a std::jthread asked to stop wakes from the wait.

Read next

  1. std::mutex in C++: how to lock it, and which lock to useConcurrency · Miriam Castellanos · written for C++17
  2. std::thread and std::jthread: start, join and stop a threadConcurrency · Miriam Castellanos · written for C++20
  3. std::atomic in C++: what it guarantees and when to use itConcurrency · Miriam Castellanos · written for C++20