用老板、打工人、任务看板例子通俗解释C++多线程中wait、join和notify_one三者联系及区别
本文通过公司角色类比解析C++多线程编程的核心机制。主线程(老板)通过条件变量(任务看板)和互斥锁(钥匙)控制子线程(员工)的工作流程:wait()让线程休眠并释放锁,notify_one()唤醒单个线程,join()等待线程结束。重点阐述了三者的原子性协作、底层实现原理及常见误区,强调循环等待和带谓词wait的重要性,并给出终止线程的最佳实践(设置标志+通知+join)。这些机制共同构成了线程同
·
一、核心概念类比
想象一个公司里的三个角色:
- 老板:负责分配任务(主线程)
- 员工:执行具体工作(子线程)
- 任务看板:传递任务信息的共享区域(条件变量和锁)
二、三个关键函数的作用
1. wait()
- 员工行为:员工发现当前没有任务(队列空),于是在看板前睡觉,同时把看板钥匙交给前台保管。
- 代码逻辑:
if (没有任务) { 放下看板钥匙; // 释放锁 开始睡觉; // 进入wait状态 拿起看板钥匙; // 被唤醒后重新获取锁 }
- 关键点:
- 原子性:放下钥匙和睡觉必须同时完成,避免其他员工在中间插入新任务而错过通知
- 自动唤醒:当老板在看板上贴了新任务并通知后,员工会自动醒来并重新拿起钥匙
2. notify_one()
- 老板行为:老板在看板上贴了新任务后,拍了拍正在睡觉的某个员工肩膀。
- 代码逻辑:
把新任务写到看板上; // 修改共享数据 拍醒一个睡觉的员工; // 调用notify_one()
- 关键点:
- 无需持有钥匙:老板不需要拿看板钥匙就能拍醒员工
- 只唤醒一个:如果有多个员工在睡觉,只随机唤醒一个
3. join()
- 老板行为:老板在下班前,必须确认某个员工已经完成所有工作并离开公司。
- 代码逻辑:
等待员工完成工作; // 调用join() 下班回家; // 主线程继续执行
- 关键点:
- 阻塞老板:老板必须等待,不能提前下班
- 资源回收:员工离开时会清理自己的工作台(释放线程资源)
三、三者的协作流程
场景:餐厅点餐系统
-
顾客下单(主线程):
{ 加锁; // 拿起菜单本(互斥锁) 把订单写到菜单本上; // 修改共享队列 解锁; // 放下菜单本 通知厨师; // notify_one() }
-
厨师等待订单(子线程):
while (餐厅营业中) { 加锁; // 拿起菜单本 if (菜单本是空的) { 解锁并等待; // wait():放下菜单本并睡觉 加锁; // 被唤醒后重新拿起菜单本 } 处理订单; // 读取共享队列 解锁; // 放下菜单本 }
-
餐厅打烊(主线程结束前):
关闭餐厅; // 设置终止标志 通知所有厨师; // notify_all() 等待所有厨师下班; // 对每个厨师线程调用join() 锁门离开; // 主线程退出
四、实现原理(简化版)
1. wait()的底层逻辑
// 伪代码表示wait()的实现
void wait(std::unique_lock<std::mutex>& lock) {
1. 记录当前线程ID到等待队列;
2. 原子性操作:
- 释放锁;
- 将线程状态设为"等待";
- 切换到内核态让线程休眠;
3. 被唤醒后:
- 重新获取锁;
- 从等待队列移除线程ID;
}
2. notify_one()的底层逻辑
// 伪代码表示notify_one()的实现
void notify_one() {
1. 检查等待队列是否有线程;
2. 如果有:
- 从队列中取出一个线程ID;
- 将该线程状态设为"就绪";
- 通知操作系统唤醒该线程;
3. 返回; // 不持有锁,不关心唤醒的线程何时执行
}
3. join()的底层逻辑
// 伪代码表示join()的实现
void join() {
1. 检查线程是否已结束;
2. 如果未结束:
- 将当前线程(主线程)设为"等待该子线程";
- 让出CPU时间,进入休眠;
3. 当子线程结束时:
- 操作系统唤醒等待的主线程;
- 回收子线程资源(栈、线程ID等);
4. 返回; // 子线程已安全结束
}
五、常见误区与最佳实践
1. 误区
- 认为notify_one()会立即唤醒线程:实际上只是标记线程可被唤醒,具体执行时机由操作系统调度
- 忘记在join()前通知线程:如果线程阻塞在wait(),不通知会导致join()永久等待
- 使用notify_one()代替join():两者作用完全不同,不能互相替代
2. 最佳实践
- 终止线程三部曲:
设置终止标志; // 如keep_running = false notify_one(); // 唤醒可能的等待 join(); // 等待线程退出
- 永远在循环中wait:
while (条件不满足) { wait(); }
- 优先使用带谓词的wait:
wait(lock, []{ return 条件满足; }); // 自动处理循环和唤醒检查
六、总结
- wait():线程释放锁并休眠,等待通知
- notify_one():唤醒一个等待中的线程(不释放锁)
- join():等待线程执行完毕并回收资源
三者结合实现了线程间的同步与协作,是C++多线程编程的核心机制。
更多推荐
所有评论(0)