
std::atomic::wait 什么时候才真正休眠
它只在三重条件同时满足时,才会调用 futex_wait(Linux)或 WaitOnAddress(Windows)进入内核级休眠;否则退化为自旋,甚至触发未定义行为。
必须同时检查:
-
std::is_trivially_copyable_v<t></t>且sizeof(T) —— 用 <code>static_assert在编译期断言,例如static_assert(std::is_trivially_copyable_v<:uint32_t> && sizeof(std::uint32_t) </:uint32_t> - 平台原语就绪:Linux 需内核 ≥ 2.6.22 + glibc ≥ 2.34,Windows 需 1903+ 并启用
WaitOnAddress;macOS 目前(2026 年)仍回退到 mutex+condvar 模拟,wait实质是锁+自旋,毫无优势 - 值严格按位匹配:不能写
counter.wait(0),而要先auto expected = counter.load(std::memory_order_acquire); counter.wait(expected)—— 否则高位随机字节会导致永远不唤醒
为什么 std::atomic ready.wait(false) 还会永久阻塞
常见错误不是类型或平台问题,而是竞态窗口和内存序缺失。
典型误写:
立即学习“C++免费学习笔记(深入)”;
while (!ready.load()) { // ❌ 这里 load 是 relaxed,默认可能被优化/缓存
ready.wait(false); // 若此时 notify 已发生,wait 永远不返回
}
正确做法:
- 等待线程必须用
std::memory_order_acquireload 建立同步点,再传入wait - 通知线程的
store必须用std::memory_order_release或更强序,否则新值可能没刷到其他 CPU 缓存行 -
store和notify_one必须紧邻,中间不能插入非原子操作,防止编译器/CPU 重排导致唤醒丢失
如何写出安全、防虚假唤醒的等待循环
std::atomic::wait 虽无标准规定的虚假唤醒,但在多核调度、信号中断、notify_all 批量唤醒等场景下,仍需按条件重检——这不是“以防万一”,而是强制要求。
正确结构只有这一种:
while (flag.load(std::memory_order_acquire) == expected) {
flag.wait(expected, std::memory_order_relaxed);
expected = flag.load(std::memory_order_acquire); // 每次 wake 后必须重读
}
关键点:
- 禁止用
if包裹wait—— 唤醒后若条件未真满足(比如被其他线程改回),直接执行后续逻辑就会出错 -
wait返回不代表值已变,只代表“可能变了”,必须重新load判断 - 若业务逻辑依赖多个变量(如
ready+ 结构体字段),仅靠atomic::wait无法保证数据一致性;需配合std::atomic_thread_fence或额外同步手段
std::atomic::wait 不能替代 std::condition_variable 的根本原因
它不是性能不够好,而是语义完全不同:一个只盯“值是否等于某数”,另一个保护“一段逻辑是否成立”。
典型陷阱:
- 没有谓词(predicate)支持:你不能写
wait([]{ return queue.size() > 0; }),只能等counter.load() == 5这种精确匹配 - 无原生超时:C++20 起虽有带
std::chrono的重载,但仍是“等值超时”,不是“等条件成立超时” - 不自动同步非原子数据:通知方
store(true, release)只保证该原子变量可见,不保证它背后初始化的数组、对象字段也同步完成 - notify_one 不保证唤醒你想要的那个线程 —— 它只是告诉内核“挑一个等这个值的线程唤醒”,由调度器决定,且可能唤醒早已过期的等待者
真正容易被忽略的是最后一点:你写了 10 个线程在不同时间点调用 wait(false),notify_one 可能唤醒第 1 个,但它醒来发现 ready 已被第 2 个线程消费并重置,于是又陷入等待——而你本意是让每个通知都对应一次处理。这种场景必须用计数器或队列,而非布尔标志。



















