关于C++ condition_variable::wait的线程阻塞相关技术疑问
C++条件变量与线程池相关问题解答
先看基础代码示例:
std::mutex thread_mutex; std::condition_variable thread_condition; void thread_func() { std::unique_lock<std::mutex> lock(thread_mutex); thread_condition.wait(lock); lock.unlock(); } std::thread t1 = std::thread(thread_func);
问题1:单个线程wait时,锁定mutex的作用是什么?
哪怕只有一个线程,锁也是必须的,核心是避免竞态条件:
- 假设主线程先调用
notify_all,子线程还没执行到wait,这时候子线程后续走到wait就会永久阻塞——因为通知已经发过了,它错过了。 - 锁的存在让
wait的“解锁+阻塞”操作和notify的“触发通知”操作形成原子性的同步,确保线程不会错过通知。 - 注意:
wait内部会自动释放mutex,线程阻塞期间是不持有锁的,不会导致其他操作被卡住。
问题2:既然wait本身会阻塞,unique_lock阻塞拿锁这一步有必要吗?
完全有必要,这是C++标准的强制要求:
- 调用
condition_variable::wait时,必须已经持有对应的mutex,否则行为是未定义的。 - 这步锁的本质是为了同步条件变量的状态检查逻辑——哪怕你没显式写条件判断,底层也依赖锁来避免“通知提前发生”的竞态。
- 而且
wait会在阻塞前自动解锁mutex,所以线程阻塞时不会占用锁,不影响其他线程操作。
添加以下代码后:
std::thread t2 = std::thread(thread_func); thread_condition.notify_all();
问题3:一个线程被unique_lock阻塞、另一个被wait阻塞时,notify_all如何通知到两个线程?
实际情况是只有被wait阻塞的线程会被这次notify_all唤醒,另一个线程会错过:
- 被
wait阻塞的线程:收到notify_all后唤醒,重新锁定mutex,然后从wait返回,之后解锁mutex。 - 被
unique_lock阻塞的线程:之前因为拿不到mutex卡在锁的构造步骤,当第一个线程解锁mutex后,它才能拿到锁并进入wait——但这时候之前的notify_all已经执行完了,所以这个线程会一直卡在wait里,除非你再次调用notify_all。
修改thread_func为循环版本后:
std::mutex thread_mutex; std::condition_variable thread_condition; void thread_func() { while(true) { std::unique_lock<std::mutex> lock(thread_mutex); thread_condition.wait(lock); lock.unlock(); } } std::thread t1 = std::thread(thread_func); std::thread t2 = std::thread(thread_func); thread_condition.notify_all();
问题4:为什么必须加谓词才能让线程持续运行?
核心原因是虚假唤醒和条件时效性:
- 虚假唤醒:操作系统可能在没有收到
notify的情况下唤醒线程(比如调度干扰),如果没有谓词,线程会从wait返回,进入下一次循环又立刻wait,但如果没有新的notify,就会一直阻塞。 - 条件时效性:就算是正常
notify唤醒,也可能出现“唤醒后条件已经不满足”的情况(比如多个线程抢任务,第一个线程把任务处理完了)。 - 带谓词的
wait会在被唤醒后自动检查条件:如果条件不满足,会自动释放mutex并重新阻塞;只有条件满足时才会返回。配合wakeup变量,每次notify前切换状态,就能确保线程在正确的时机被唤醒并继续循环。
内容的提问来源于stack exchange,提问作者Rhino R.
相关产品推荐
相关产品推荐

