C++11条件变量notify_all、notify_one与虚假唤醒相关问题
C++11 条件变量相关问题解答
为什么保留std::notify_all而非统一使用std::notify_one
notify_all是保障同步逻辑正确性的必要接口,并非冗余设计,它的不可替代性主要体现在三类场景:
- 多条件分支等待场景:当多个等待线程的唤醒判定条件不一致时,发起通知的线程根本无法预判当前状态变更满足了多少个线程的等待条件。比如同一个条件变量上,有的线程等待“队列非空可消费任务”,有的等待“队列非满可生产任务”,还有的等待“停止标志为真可退出线程”,此时如果只调用
notify_one,极有可能唤醒的是判定条件尚未满足的线程,导致真正满足条件的线程永久阻塞,触发死锁。 - 全量唤醒场景:比如线程池退出、同步屏障放行这类需要所有等待线程同时推进的场景,必须使用
notify_all。以线程池析构流程为例,设置停止标志后如果只调用notify_one,每次仅能唤醒一个工作线程,剩余线程会一直阻塞在条件变量上无法回收,直接造成资源泄漏。 - 低开销兜底场景:主流操作系统的条件变量实现中,持锁状态下调用
notify_all不会触发无意义的惊群效应——被唤醒的线程会自动排队等待互斥锁,不会出现大量线程同时抢占CPU的情况,此时notify_all的实际开销和多次调用notify_one没有本质区别,还能从根源上避免漏唤醒的风险。
notify_all唤醒多线程后仅一个拿到锁,其余重回阻塞是否属于虚假唤醒
完全不属于虚假唤醒。
首先明确std::condition_variable::wait的标准执行流程:
- 原子释放当前线程持有的互斥锁,将线程加入条件变量等待队列
- 阻塞等待唤醒信号
- 收到唤醒信号后,重新竞争互斥锁,只有成功拿到锁的线程才会从
wait调用返回、执行后续用户代码notify_all的语义本身就是通知所有等待线程“共享状态可能发生变更,请重新检查条件”,未抢到锁的线程只是阻塞在互斥锁的等待队列上,根本没有从wait调用返回,自然谈不上“被唤醒”。后续这些线程拿到锁返回后,通过条件检查发现谓词不满足则继续等待,是条件变量的标准使用模式,和虚假唤醒没有关联。
C++标准对虚假唤醒的定义非常明确:线程在没有任何线程调用notify接口、没有触发超时、也没有收到系统信号的情况下,无理由从wait调用返回,这类底层实现允许的无理由返回才是虚假唤醒。
std::notify_one是否会引发虚假唤醒
会。虚假唤醒是操作系统层面条件变量实现的固有特性,和上层调用notify_one还是notify_all没有任何关系。
包括Linux futex、POSIX条件变量在内的主流底层同步原语,都明确允许虚假唤醒的存在——这种设计是为了简化底层实现逻辑,减少内核态和用户态的状态同步开销,哪怕没有任何线程调用notify接口,等待的线程也有可能因为系统信号、内核调度竞态等原因意外返回。
这也是为什么条件变量的使用必须绑定谓词检查:要么使用cv.wait(lock, predicate)的重载版本,要么手动编写循环在wait返回后检查条件,绝对不能假设wait返回时等待的条件一定成立。
内容的提问来源于stack exchange,提问作者f1msch
相关产品推荐
相关产品推荐

