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
Abstract
A condition variable in C++, std:: 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.

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::, 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:
#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:
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::; before C++17, write std::, and the same for std::lock_guard. std:: 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:: (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:
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.

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:: works only with a std::, 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:
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:
processing job 1
processing job 2
processing job 3
processing job 4
processing job 5
consumer done— end example]
What makes it end cleanly:
doneis set under the same mutex as the jobs, andnotify_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:
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:: or std::, 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:: has wait overloads that take one, so a std::jthread asked to stop wakes up with no done flag of your own; std:: has none. The queue again, with cv declared as a std:: and the consumer taking its std::jthread’s stop token:
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_lockand a predicate, never a barewait. 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
boolit 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::, 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::, 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:: is a condition variable that works with any lock type that has lock() and unlock(). std:: accepts only a std::unique_lock of std::mutex, which cppreference says allows maximal efficiency on some platforms. Since C++20, std:: also has wait overloads that take a std::stop_token, so a std::jthread asked to stop wakes from the wait.
Sources
- std::condition_variablecppreference.com
- std::condition_variable::waitcppreference.com
- std::condition_variable::wait_forcppreference.com
- std::condition_variable::notify_onecppreference.com
- std::condition_variable_any::waitcppreference.com
- std::counting_semaphorecppreference.com
- [thread.condition.condvar] Class condition_variableC++ standard working draft