Wiki
condition_variable
std::condition_variable Used to solve this type of problem:
A certain condition is not yet satisfied. I do not want to keep occupying the CPU to check, but instead want to sleep first and wake up when the condition may have changed.
Typical scenarios include:
队列里暂时没有任务
→ worker 先睡眠
初始化还没完成
→ 其他线程先等待
消费者暂时没有数据
→ 等生产者放入数据
多个线程等待某个状态变为 ready
→ 状态变化后再继续
The most important thing about condition variables is not to memorize:
wait()
notify_one()
notify_all()
Instead, understand that it must be used together with the following set of things:
共享状态 + mutex + condition_variable + predicate
Their division of labor is:
共享状态
→ 记录“事情到底有没有发生”
mutex
→ 保护共享状态的读写
condition_variable
→ 条件不满足时让线程睡眠
→ 条件可能变化时把等待线程叫醒
predicate
→ 被唤醒以后重新判断:
“现在到底能不能继续执行?”
Therefore:
condition_variableIt does not itself save business state; it is merely a "wait and wake-up mechanism".
1.Condition Variable Basics
1.1.Why can't it keep looping the check?
The most direct waiting method may be:
while (!ready)
{
}
This code has two problems.
First, if:
bool ready = false;
If it is an ordinary variable and multiple threads read and write it simultaneously, the following may occur:
data race
Even if changed to:
std::atomic<bool> ready{false};
Then:
while (!ready.load())
{
}
Although it is thread-safe, the thread will keep executing:
load
load
load
load
load
...
This is called:
Busy waiting
That is, although the thread is not doing anything useful, it keeps occupying the CPU to check the condition.
If it might take milliseconds, seconds, or even longer, this is usually wasteful.
The idea of a condition variable is:
条件不满足
↓
线程睡眠,不占着 CPU 空转
其他线程修改状态
↓
发出通知
等待线程醒来
↓
重新检查条件
So what the condition variable solves is not:
怎么让 bool 线程安全
Instead:
条件不满足时怎么高效等待
条件可能满足时怎么把线程叫醒
1.2.The three most basic objects of a condition variable
Usually, you will see:
std::mutex mutex;
std::condition_variable cv;
bool ready = false;
Here:
ready
is true shared state, meaning:
“现在能不能继续执行?”
mutex:
std::mutex mutex;
Responsible for protection:
ready
read and write.
cv:
std::condition_variable cv;
Responsible for:
让线程睡眠
+
把线程唤醒
The most important point:
condition_variableis not itself a state.
That is to say, the following idea is wrong:
“我收到 notify 了,所以条件肯定成立了。”
What truly determines whether the thread can continue is:
ready
Or:
!queue.empty()
or other business statuses.
1.3.Simplest wait / notify example
#include <condition_variable>
#include <mutex>
#include <print>
#include <thread>
std::mutex mutex;
std::condition_variable cv;
bool ready = false;
void worker()
{
std::unique_lock lock(mutex);
cv.wait(lock, [] {
return ready;
});
std::println("worker start");
}
int main()
{
std::thread thread(worker);
{
std::lock_guard lock(mutex);
ready = true;
}
cv.notify_one();
thread.join();
}
Output result:
worker start
The entire process can be understood as:
worker 获得 mutex
↓
检查 ready
↓
发现 ready == false
↓
wait():
释放 mutex
并让 worker 睡眠
↓
main 获得 mutex
↓
ready = true
↓
main 释放 mutex
↓
notify_one()
↓
worker 被唤醒
↓
worker 重新获得 mutex
↓
重新检查 ready
↓
ready == true
↓
wait() 返回
↓
打印 worker start
The most important step here is:
wait() 会在睡眠期间释放 mutex
Otherwise, the program will deadlock.
1.4.Why must the mutex be released when wait() sleeps?
Suppose the worker does this:
拿到 mutex
↓
发现 ready == false
↓
拿着 mutex 睡觉
Then main wants to execute:
ready = true;
Previously, it is also necessary to first:
lock(mutex);
But the mutex is being held by the worker the whole time.
Therefore:
worker:
等 ready 变成 true
但一直拿着 mutex
main:
想把 ready 改成 true
但拿不到 mutex
The result is:
谁也进行不下去
So the condition variable's wait() must do something very special:
当前已经持有 mutex
↓
原子地释放 mutex + 进入等待
↓
收到唤醒
↓
重新获得 mutex
↓
再返回
1.5.Why must wait() be used with std::unique_lock
Common notation:
std::unique_lock lock(mutex);
cv.wait(lock, predicate);
rather than:
std::lock_guard lock(mutex);
The reason is:
wait() Required temporarily:
unlock()
Then later:
lock()
and std::unique_lock supports this:
临时释放锁
↓
以后重新获得锁
ownership management.
std::lock_guard Manual unlocking and relocking are not supported, so it cannot satisfy the requirements of ordinary condition_variable::wait().
You can:
cv.wait(lock, predicate);
Roughly understood as:
while (!predicate())
{
cv.wait(lock);
}
And the internal:
cv.wait(lock);
Can do:
释放 mutex
+
线程睡眠
+
被唤醒
+
重新获得 mutex
1.6.Why must “release mutex + sleep” be atomic?
There is another very important issue here.
Assume that the internal logic of wait is not atomic, but rather:
1. unlock mutex
2. 过一会儿才真正开始睡眠
Then it may happen:
worker:
unlock mutex
↓
还没真正睡下去
main:
修改 ready = true
↓
notify_one()
worker:
此时才进入睡眠
Like so:
notify 已经发生了
worker 却刚刚开始睡
the worker may keep sleeping indefinitely.
Therefore, condition variables must guarantee the following:
No gap is left between "releasing the mutex" and "entering the wait" in which a notification could be lost.
This is also why you should not use it yourself:
unlock();
sleep();
lock();
Go simulate condition_variable::wait().
2.wait and predicate
2.1.wait(lock) and wait(lock, predicate)
There are two common waiting modes for condition variables.
2.1.1.without predicate
cv.wait(lock);
It means:
先睡眠
↓
被唤醒后重新拿锁
↓
返回
The question is:
Being awakened does not mean that your business conditions are necessarily met.
So if this form is used, generally one must write the loop oneself:
while (!ready)
{
cv.wait(lock);
}
2.1.2.with predicates
Recommended:
cv.wait(lock, [] {
return ready;
});
It can be understood as the standard library having written it for you:
while (!ready)
{
cv.wait(lock);
}
Therefore:
cv.wait(lock, predicate);
What is truly expressed is:
As long as the predicate does not hold yet, I will continue to wait.
For example:
cv.wait(lock, [] {
return !queue.empty();
});
Indicates:
As long as the queue remains empty, I will continue waiting.
2.2.What is a predicate
predicate is:
The condition for determining whether the process can continue now.
For example:
[] {
return ready;
}
Or:
[] {
return !queue.empty();
}
Or:
[] {
return !queue.empty() || finished;
}
Therefore:
condition_variable
→ 负责把你叫醒
predicate
→ 决定你醒来以后到底能不能继续
It can be remembered as:
notify only says "get up and take a look"; predicate says "whether it can proceed now".
2.3.Spurious wakeup
Condition variables allow a situation:
没有真正的业务事件发生
↓
wait() 却醒了
This is called:
Spurious wakeup
So this way of writing is unsafe:
cv.wait(lock);
use_shared_data();
Because:
wait() 返回
It does not necessarily mean:
共享数据已经满足条件
The correct way to write it is:
cv.wait(lock, [] {
return ready;
});
Or equivalently:
while (!ready)
{
cv.wait(lock);
}
In other words:
Every time it wakes up, it must re-examine the true shared state.
3.notify and status
3.1.notify is not state
There is another aspect of condition variables that is very easily misunderstood:
cv.notify_one();
Not in:
condition_variable 里面存了一条消息
It is more like:
“Those who are currently waiting for someone can stand up and re-examine the conditions.”
If notification occurs:
根本没有线程在等待
That concludes this notification.
Threads that wait later:
不会补收到这次旧通知
For example:
线程 A:
notify_one()
↓
当时没人等待
↓
通知结束
过了一会儿
线程 B:
开始 wait()
Thread B will not receive the previous notification from thread A.
This type of phenomenon is usually called:
Lost wakeup
3.2.Then why does it not easily go wrong because "the notification occurs first"
Because correct code does not rely on:
“通知有没有发生过”
Instead, it relies on:
“共享状态现在是什么”
For example:
{
std::lock_guard lock(mutex);
ready = true;
}
cv.notify_one();
Even if:
ready = true
notify_one()
They all occur before the worker starts to wait, so there is no problem either.
Because the worker later executes:
cv.wait(lock, [] {
return ready;
});
It will first check:
ready
Found:
true
Therefore:
根本不会睡
Therefore:
共享状态
→ 保存事实
condition_variable
→ 只是优化等待方式
This is one of the most core understandings of condition variables.
3.3.notify_one()
cv.notify_one();
Indicates:
Wake up a thread that is currently waiting for this condition_variable.
For example, there are:
10 个 worker 都在等任务
But currently only the following have been placed:
1 个任务
Usually:
notify_one();
That is enough.
Because ultimately only one worker can consume this task.
3.4.notify_all()
cv.notify_all();
Indicates:
Wake up all threads that are currently waiting for this condition_variable.
Suitable for:
程序准备退出
全局状态发生变化
所有 worker 都应该重新检查退出条件
For example:
finished = true;
cv.notify_all();
All threads will be woken up.
However:
notify_all()
Does not represent:
所有线程同时进入临界区
After they wake up, they still need to:
竞争同一把 mutex
↓
一个一个拿锁
↓
检查 predicate
Therefore:
notify_all()
→ 所有人都醒来看看
不是
→ 所有人同时进入临界区
3.5.After modifying the state, when should notify be called?
A very common way to write this is:
{
std::lock_guard lock(mutex);
ready = true;
}
cv.notify_one();
The sequence is:
持锁修改共享状态
↓
释放 mutex
↓
notify
In this way, after the waiting thread is awakened:
更有机会直接拿到 mutex
rather than:
刚醒
↓
发现通知线程还拿着 mutex
↓
又继续阻塞
However, note:
notify()It does not prescribe that "it must always be placed outside the lock".
For example:
{
std::lock_guard lock(mutex);
ready = true;
cv.notify_one();
}
It is also completely correct in many scenarios.
The core that truly determines correctness is:
共享谓词状态必须正确同步
wait 方必须在锁保护下检查 predicate
Putting notify outside the lock is more of a common performance and scheduling optimization habit.
4.Producer-Consumer
4.1.Producer-consumer model
This is the most classic application of condition variables.
Assumption:
producer
→ 不断往 queue 塞数据
consumer
→ 没数据时睡眠
→ 有数据时取出来处理
At the same time, it is also necessary to consider:
producer 最终会结束
Therefore, the condition that consumers are truly waiting for cannot be merely:
!queue.empty()
Also consider:
finished
Complete example:
#include <condition_variable>
#include <mutex>
#include <print>
#include <queue>
#include <thread>
std::queue<int> queue;
std::mutex mutex;
std::condition_variable cv;
bool finished = false;
void producer()
{
for (int value = 1; value <= 5; ++value)
{
{
std::lock_guard lock(mutex);
queue.push(value);
}
cv.notify_one();
}
{
std::lock_guard lock(mutex);
finished = true;
}
cv.notify_all();
}
void consumer()
{
while (true)
{
int value;
{
std::unique_lock lock(mutex);
cv.wait(lock, [] {
return !queue.empty() || finished;
});
if (queue.empty() && finished)
{
break;
}
value = queue.front();
queue.pop();
}
std::println("consume {}", value);
}
}
int main()
{
std::thread producer_thread(producer);
std::thread consumer_thread(consumer);
producer_thread.join();
consumer_thread.join();
}
Output:
consume 1
consume 2
consume 3
consume 4
consume 5
4.2.Why is the predicate:
!queue.empty() || finished
rather than:
!queue.empty()
Assume the producer has completely finished:
finished == true
At the same time, the queue is also empty:
queue.empty() == true
If the consumer only waits:
!queue.empty()
Then:
以后永远不会再有数据
↓
consumer 却继续等待“队列非空”
↓
没人会再生产任务
↓
consumer 永远睡下去
Therefore, the correct waiting condition should express:
有任务了
或者
生产已经结束了
That is:
!queue.empty() || finished
4.3.Why does the consumer still need to check after waking up?
After the wait is over:
cv.wait(lock, [] {
return !queue.empty() || finished;
});
Here, only the following is explained:
队列非空
或者
生产结束
At least one is true.
So the next step is to distinguish:
4.3.1.Situation: the queue has data
queue 非空
↓
取出一个任务
4.3.2.Condition: the queue is empty, and finished == true
queue.empty()
&&
finished
Description:
现在没任务
而且以后也不会再有任务
Only then can the consumer exit safely.
4.4.Why should the actual processing of the task be placed outside the lock?
Inside the consumer:
{
std::unique_lock lock(mutex);
// 检查 queue
// pop 一个任务
}
process_task();
General ratio:
std::unique_lock lock(mutex);
queue.pop();
process_task(); // 耗时 2 秒
Better.
The reason is:
If you hold the mutex while doing time-consuming work:
consumer 拿着 queue mutex 处理 2 秒
So within these 2 seconds:
producer 不能 push
其他 consumer 不能 pop
All threads are blocked by this one lock.
Therefore, within the lock, try to only do:
检查共享状态
读取 / 修改共享状态
取出任务
The truly time-consuming business logic:
放到锁外
This is a very important habit in multithreaded programs.
5.Timeouts and Advanced Usage
5.1.wait_for()
Can make the thread:
Wait at most for a period of time.
For example:
bool ok = cv.wait_for(
lock,
std::chrono::seconds(1),
[] {
return ready;
});
Return:
true
→ 在等待结束前 predicate 成立
false
→ 超时以后 predicate 仍然不成立
For example:
if (!cv.wait_for(
lock,
std::chrono::seconds(1),
[] {
return ready;
}))
{
std::println("timeout");
}
Note:
timeout
It does not mean the program will necessarily fail.
It only indicates:
这段时间里条件没有成立
As for after the timeout:
重试
退出
打印日志
走备用逻辑
Determined by the business.
5.2.wait_until()
wait_until() Used to wait until a certain:
绝对时间点
For example:
auto deadline =
std::chrono::steady_clock::now()
+ std::chrono::seconds(2);
cv.wait_until(
lock,
deadline,
[] {
return ready;
});
It can be understood as:
一直等
直到:
ready == true
或者
到达 deadline
If it were merely:
等 2 秒
Usually:
wait_for(...)
More intuitive.
If you already have:
明确截止时间
Then:
wait_until(...)
is more appropriate.
5.3.Why is it usually recommended that the timeout steady_clock
When it involves "how long it lasts":
std::chrono::steady_clock
Generally, it is more appropriate.
Because it is a monotonic clock, it will not be affected by:
用户手动改系统时间
NTP 校时
系统时间向前 / 向后跳
but changes suddenly.
So this kind of code:
auto deadline =
std::chrono::steady_clock::now()
+ 2s;
It is usually more appropriate than wall-clock time-based:
超时
计时
等待
5.4.Can a condition_variable wait on multiple conditions?
Of course.
What really determines the wait condition is:
predicate
For example:
cv.wait(lock, [] {
return !queue.empty()
|| stopped
|| error;
});
Indicates:
只要下面任何一个发生:
队列有数据
或
程序停止
或
发生错误
就结束等待并继续处理
Then after waking up, make a judgment:
if (error)
{
// 处理错误
}
else if (stopped && queue.empty())
{
// 退出
}
else
{
// 处理队列任务
}
Therefore:
condition_variable
It does not care how many conditions you are actually waiting for.
It is only responsible for:
睡
+
醒
Complex conditions all consist of:
predicate
Decision.
5.5.However, don't write the predicate too messily.
If you start writing:
cv.wait(lock, [] {
return
a && b
|| c && !d
|| error_code != 0
|| queue.size() > 3
|| ...
});
This indicates that the shared state design may already be starting to become complex.
At this point, consider:
能不能定义更清楚的状态对象
能不能使用 enum 表示状态
能不能拆分不同等待条件
能不能简化线程之间的协议
instead of piling all business logic into one giant predicate.
5.6.condition_variable_any
The standard library also includes:
std::condition_variable_any
Normal:
std::condition_variable
Main sum:
std::unique_lock<std::mutex>
Coordination.
And:
std::condition_variable_any
Can work with a broader range of lock types.
Therefore it:
更灵活
But usually:
也可能有更多开销
If it were merely:
std::mutex
Ordinary scenarios take priority:
std::condition_variable
That is enough.
5.7.condition_variable_any and stop_token
C++20 Medium:
std::condition_variable_any
There is also a very practical point:
can cooperate with
std::stop_tokenwaiting.
For this:
std::jthread
Very convenient.
For example, the worker is:
等待队列任务
At the same time, the program also hopes that:
request_stop()
Later, it can be woken up from waiting and made to exit.
At this point:
condition_variable_any
+
stop_token
we can then:
业务条件成立
或者
收到停止请求
Combine into the same waiting process.
5.8.condition_variable How to choose between atomic::wait and
After C++20:
std::atomic
Also supports:
wait()
notify_one()
notify_all()
If it were merely:
等待一个 atomic 值变化
For example:
std::atomic<bool> ready{false};
ready.wait(false);
This scenario:
atomic::wait
It will be very convenient.
If the wait condition is:
queue 非空
或者 finished == true
或者 error != 0
This category involves:
多个共享变量
容器
复杂业务状态
Usually:
mutex
+
condition_variable
+
predicate
More natural.
It can be simply remembered as:
只围绕一个 atomic 值等待
→ atomic::wait()
围绕一组共享业务状态等待
→ condition_variable
6.Common Errors
6.1.Error: treating notify as a state
Incorrect idea:
“notify 已经调用了,
所以以后 wait 一定知道。”
Incorrect.
cv.notify_one();
A message will not be permanently stored.
The real state must be placed in:
ready
Or:
queue
Or:
finished
in.
6.2.Error: Do not use predicate to handle spurious wakeups
Not recommended:
cv.wait(lock);
use_data();
Should:
cv.wait(lock, [] {
return ready;
});
6.3.Error: The waiter locks, but the modifier performs a bare write to the shared variable
For example:
bool ready;
worker:
std::unique_lock lock(mutex);
cv.wait(lock, [] {
return ready;
});
main but:
ready = true; // 没有锁
This may still result in a data race.
For this kind of ordinary shared state:
读和写
should all follow the same set of mutex synchronization rules.
6.4.Error: holding lock while doing time-consuming work
Error:
std::unique_lock lock(mutex);
auto task = queue.front();
queue.pop();
do_heavy_work(task);
Better:
Task task;
{
std::unique_lock lock(mutex);
task = queue.front();
queue.pop();
}
do_heavy_work(task);
Release the lock as soon as possible.
6.5.Error: when stopping, only finished was modified, but notify was forgotten
For example:
{
std::lock_guard lock(mutex);
finished = true;
}
If the worker is:
cv.wait(...)
It may still be asleep.
Usually, the following is also required:
cv.notify_all();
Wake up the waiting threads and let them recheck:
finished
6.6.Error: Only considered "no task now", but did not consider "there will be no task in the future either"
The producer-consumer model must be designed with:
什么时候表示生产彻底结束
Otherwise, the consumer will easily:
队列空
↓
继续 wait
↓
但 producer 已经退出
↓
以后永远没人再 notify
So usually, it is necessary to:
bool finished;
or other explicit termination states.
7.Final summary
std::condition_variableThe most essential thing is not:
notify_one()
but rather this entire set of relationships:
共享状态
+
mutex
+
condition_variable
+
predicate
You can remember it like this:
共享状态
→ 记录事实
mutex
→ 保护事实
condition_variable
→ 负责睡眠和唤醒
predicate
→ 决定醒来后到底能不能继续
wait() Core Process:
拿着 mutex
↓
predicate 不成立
↓
释放 mutex + 睡眠
↓
收到 notify
↓
重新获得 mutex
↓
重新检查 predicate
↓
条件成立才真正返回
A few other key points:
notify_one()
→ 唤醒一个等待者
notify_all()
→ 唤醒所有等待者
wait_for()
→ 最多等一段时间
wait_until()
→ 最多等到某个时间点
Finally, always remember:
condition_variable There is no "memory"; what actually preserves state is a shared variable.
And:
Being woken up by notify does not mean the condition is satisfied; the predicate must always ultimately be rechecked.