首先,需要明确几个关键要点:std::atomic::wait 是 C++20 引入的新特性,其核心价值在于提供一种轻量级的原子变量等待机制,但实际使用中存在诸多细节。如果对编译器版本、平台兼容性、或底层同步机制缺乏深入了解,很容易陷入误区。

编译器支持:使用std::atomic::wait的第一道关卡
std::atomic::wait 依赖于 std::atomic 的等待/通知机制,其底层实现类似于 futex。然而,并非所有编译器都默认启用了这项支持。
只有 GCC 11 及以上、Clang 12 及以上、MSVC 19.30 及以上版本才完整实现了该机制。使用时必须显式启用 C++20 模式,即添加编译选项 -std=c++20。否则,编译时可能直接报错,或者退化为忙等待(fallback busy-wait),从而失去使用它的意义。
判断编译器是否支持,最直接的方法是在代码中添加编译时断言:
#include
static_assert(std::atomic::is_always_lock_free); // 基础保障
static_assert(__cpp_lib_atomic_wait >= 201907L); // C++20 wait 特性宏
- 在 Linux 平台上,还需要 glibc 版本 ≥ 2.34。若不满足条件,即使编译通过,
wait也可能静默退化为 fallback 行为,造成性能下降。 - macOS 的情况更为复杂。截至 macOS 14,不管是 libstdc++ 还是 libc++,都未真正实现
wait。强行调用会抛出std::system_error异常,甚至导致程序崩溃。 - 在 Windows 上,MSVC 已经提供支持,但 MinGW-w64 普遍仍不支持。
wait 与 notify 必须成对使用
许多开发者容易误解 wait 的工作原理。它并非轮询操作——实际上,它会将当前线程挂起,直到被显式唤醒或遭遇虚假唤醒。如果没有其他线程调用 notify_one 或 notify_all,该线程可能会永久阻塞(除非使用带超时的重载版本)。
最常见的错误是只编写了 wait,却没有对应的 notify。或者,notify 发生在错误的线程时机,例如在 wait 之前就发出了通知,且没有通过内存序保证可见性,从而使得通知被浪费:
std::atomicflag{0};
// 线程 A:
flag.wait(0); // 等待 flag != 0
// 线程 B:
flag.store(1, std::memory_order_relaxed);
flag.notify_one(); // ✅ 正确:store 后 notify
// ❌ 错误:先 notify 再 store → A 可能永远等不到
以下是几个需要牢记的要点:
- notify 必须在 wait 已经观察到旧值之后发出。建议使用
memory_order_seq_cst,或者至少搭配memory_order_release的 store 和memory_order_acquire的 load。 notify_one与notify_all的性能差异显著。notify_all 唤醒所有等待线程,适用于广播场景;notify_one 则更轻量,通常用于一对一同步。- 关于
wait的参数:它传入的是“期望值”。也就是说,仅当原子变量的值**等于**该期望值时,线程才会被挂起。一旦值发生变化(即使只是临时变化),wait就会返回。
虚假唤醒:一个老问题,仍需警惕
wait 可能无缘无故返回,这就是所谓的虚假唤醒。因此,绝不能假设返回即意味着收到了 notify。正确的做法是始终使用循环检查条件是否真正满足:
std::atomicready{false};
// ✅ 正确写法
while (!ready.load(std::memory_order_acquire)) {
ready.wait(false); // 等待 ready 变成 true
}
// ❌ 错误:一次 wait 不够
ready.wait(false); // 可能虚假唤醒,此时 ready 仍是 false
补充几点:
- 在循环中重新加载(reload)是必需的,否则无法区分真实状态变化还是虚假唤醒。
- reload 时的内存序应与 wait 之前的判断保持一致,通常使用
memory_order_acquire。 - 如果条件较为复杂(例如需要联合判断多个原子变量),
wait无法直接支持。此时,应退回到使用 mutex 配合 condition_variable 的方案。
超时版本的陷阱:不要当作万能药
wait 提供了一个带 std::chrono::duration 的重载。但它的超时语义并非“最多等待 X 时间”,而是“至少等待 X 时间后,检查一次当前值”。实际唤醒时间可能会显著延迟,尤其在系统负载高或调度不及时的情况下。
需要留意以下细节:
- 在 Linux 上,基于 futex 的超时相对精确;而在 Windows 上,基于 SRWLock 的实现,延迟可能达到毫秒级别。
- 超时后,函数返回,但不会修改原子变量的值,也不保证后续的 notify 是否仍然有效(notify 可能在超时前已发出,但未能送达)。
- 不要用
wait_for替代心跳检测。它不提供周期性回调能力,仅是一次性等待。 - 如果应用场景确实需要可靠的定时等待组合,更稳妥的做法是使用独立的 timer 线程,配合
notify实现,而不是依赖wait的超时精度。
高性能等待的核心,并不在于“如何等待”这个动作本身,而在于“谁来通知、何时通知、通知是否被正确接收”。wait 虽然轻量,但其正确性完全依赖于 notify 的时机和内存序——这一点,远比写出几行代码更为复杂。
