C++20 atomic::wait 与 notify:等待状态变化,不必一直空转
如果线程只需要等待一个原子状态改变,循环 load 加 sleep 虽然容易写,却很难兼顾延迟与资源占用。C++20 为原子对象增加 wait、notify_one 和 notify_all,让这种简单等待更直接。
最低标准:C++20,且标准库需要实现原子等待。下面是一次性数据发布,不是通用消息队列。
1. 改状态,然后通知
#include<atomic>#include<iostream>#include<string>#include<thread>intmain(){std::atomic<bool>ready{false};std::string message;std::threadconsumer([&]{ready.wait(false,std::memory_order_acquire);std::cout<<message<<'\n';});message="data is ready";ready.store(true,std::memory_order_release);ready.notify_one();consumer.join();}输出 data is ready。消费者等待 ready 不再等于 false;生产者先写数据,再以 release 存储 true,然后唤醒等待者。对应的 acquire 观察到这次发布后,消费者才能安全读取此前写好的普通字符串。
2. 通知不是状态本身
只 notify 不改变值,不能让等待条件成立。反过来,只 store 改值却不 notify,也不能保证已经阻塞的等待者及时被唤醒。常见正确顺序是先更新状态,再通知。
若生产者在消费者进入 wait 前已经把值改为 true,wait 检查到当前值不同,会直接返回,因此一次性状态不会因为“通知早了一点”就丢失。
3. wait 比较的是值,不记录事件次数
底层等待可能被虚假唤醒,但 atomic::wait 会继续检查,只在观察到值不等于给定旧值时返回。不过,值若从 A 变为 B 又变回 A,等待者可能完全错过中间变化,这就是需要注意的 ABA 问题。
所以,不能用一个 bool 的快速翻转来可靠计数每一次事件。需要消息、多个任务或事件数量时,应设计队列、计数协议,或者使用适合的同步原语。
4. 内存序和生命周期都不能省略思考
notify 本身不替代 release/acquire 的数据发布关系。若改成 relaxed 并仍让两个线程无保护地读写同一个普通对象,就不能凭“已经通知了”断言安全。
示例中 ready 与 message 活到消费者 join 之后,而且 message 在发布后不再被并发修改。通用组件也必须保证等待中的原子对象不被销毁。
C++20 的原子等待没有直接的带超时成员函数;要等待复杂谓词或实现超时,condition_variable 往往更合适。简单单状态等待用 atomic::wait,复杂状态协调仍要选择与需求匹配的工具。
进一步阅读:C++ 标准草案中的原子等待操作。