std::atomic::wait/notify 不是 condition_variable 的轻量替代品,它们仅在原子值精确匹配、平台支持底层原语且类型满足 trivial 和大小限制时才高效休眠;notify_one 不保证唤醒顺序,必须配合 while 循环重检状态;其无锁特性无法保护关联非原子数据,易致读取脏数据。

std::atomic::wait 和 notify 不是 condition_variable 的轻量版,它们根本不是用来干同一件事的——用错场景,线程会醒得莫名其妙,数据会读得支离破碎。
std::atomic::wait 什么时候才真正休眠
它只在三个条件同时成立时进入高效内核等待(否则退化为自旋):
- 当前原子值
load()结果与传入的expected参数按位完全相等(注意:不是“逻辑相等”,比如int64_t存了 32 位值但高位有垃圾,wait(0)就永远不触发) - 目标平台支持底层原语(Linux 需 futex,Windows 需
WaitOnAddress,macOS 目前仍回退到 mutex+condvar 模拟) - 原子类型大小 ≤ 指针宽度,且满足
std::is_trivially_copyable_v<t></t>(C++27 才放宽限制)
常见误写:counter.wait(0);正确写法应是先 int expected = counter.load(); 再 counter.wait(expected),确保视图一致。
notify_one 唤醒谁?别猜,也别依赖顺序
notify_one 不唤醒“最先等的那个”,也不保证唤醒你期望的那个线程——它只是通知内核:“有人在等这个原子变量,挑一个唤醒吧”,最终由调度器决定。更关键的是:
立即学习“C++免费学习笔记(深入)”;
- 被唤醒的线程必须重新检查状态(因为存在虚假唤醒),不能直接假设条件已满足
-
notify_all是唤醒所有当前阻塞在该原子变量上的线程,不是广播给所有曾等过的人 - 若多个线程在不同时间点调用
wait,但共享同一个expected值,notify_one可能唤醒一个早已过期的等待者
所以实际编码中,几乎总要配合 while (state.load() == expected) 循环使用,而不是 if。
为什么不能用 atomic::wait 替代 condition_variable::wait
核心差异不在性能,而在锁语义:
-
std::condition_variable::wait必须和std::unique_lock绑定,内部自动解锁/重锁,保护的是整个临界区数据 -
std::atomic::wait完全不碰锁,它只盯着那个原子变量本身是否变化;它无法阻止其他线程并发修改该变量所代表状态背后的非原子数据 - 典型错误:用
std::atomic<bool> ready</bool>表示“结构体已初始化完毕”,但写线程只对ready调用store(true, memory_order_release),却没对结构体字段加内存序约束;读线程ready.wait(false)唤醒后直接访问结构体字段,可能看到部分更新的脏数据
这种问题不会报错,但结果不可预测,且难以复现。
struct Status 类型的 wait 要特别注意对齐和加载顺序
C++27 允许 std::atomic<status></status> 使用 wait(),但前提是 Status 是 trivial 且大小 ≤ 指针宽度。即便如此,仍有两个易忽略点:
- 结构体字段顺序影响内存布局,若
char phase在前、int code在后,且未显式对齐,load()可能读出中间态(例如phase='I'但code还是旧值) - 每次
wait()前必须重新load()当前值作为expected,不能复用上一次的局部变量——因为虚假唤醒后,真实值可能已经变过好几轮 - 若结构体含 padding,某些平台对未初始化 padding 的按位比较会失败,导致
wait()永远不返回
最稳妥做法:把状态压缩进单个整数(如 bitfield 或 enum class + std::atomic<int></int>),或使用 std::atomic<:uint64_t></:uint64_t> 手动序列化结构体。


















