C++线程join()工作原理及代码无死锁原因咨询
解答:两段线程同步代码的死锁疑问与
std::thread::join()机制解析 一、第一段代码为何不会死锁?
先梳理这段代码的执行流程和锁的交互逻辑:
- 初始状态:主线程
main首先调用lock_bar_.lock(),独占了lock_bar_锁。 - 线程启动:启动
t1(执行foo)和t2(执行bar):t2进入bar函数,第一行就是lock_bar_.lock(),但lock_bar_已经被主线程持有,所以t2立即阻塞,等待锁释放。t1进入foo函数,调用lock_foo_.lock()(此时lock_foo_无人持有,成功获取),然后执行输出,接着调用lock_bar_.unlock()——这一步释放了主线程持有的lock_bar_锁。
- 线程交替执行:
lock_bar_被释放后,阻塞的t2立即获取到锁,执行输出,然后调用lock_foo_.unlock()释放lock_foo_。t1的下一次循环又能获取lock_foo_,重复上述流程:输出→释放lock_bar_,让t2继续执行。
死锁的发生需要满足四个必要条件:互斥访问、持有并等待、不可剥夺、循环等待。这段代码完全不满足「循环等待」和「持有并等待」:
- 每个线程不会同时持有两个锁:
foo只持有lock_foo_,释放的是lock_bar_;bar只持有lock_bar_,释放的是lock_foo_。 - 锁的获取没有形成循环依赖:线程之间不存在“我拿着你要的锁,你拿着我要的锁”的情况,始终是单向的锁释放触发下一个线程的执行。
正因为如此,这段代码只会让t1和t2交替执行,绝不会出现死锁。
二、std::thread::join()的实际工作机制与第二段代码无死锁原因
关于std::thread::join()的误解澄清
你看到的cppreference描述完全准确:join()会阻塞调用它的线程(这里是主线程),直到目标线程(t1)完全执行完毕。但你觉得“实际运行现象不符”,大概率是误解了「阻塞主线程」的含义——它只是让主线程暂停执行后续代码,并不会阻止其他线程(比如t2)的运行。
在第二段代码中,t1的执行流程是:
- 每次循环获取锁后,会调用
cv1.wait(ul, [&](){return first;}):如果first为false,wait()会释放锁并让t1进入阻塞状态,等待条件变量的通知。 - 当
t2执行完输出并调用cv1.notify_all()后,t1才会被唤醒,重新检查条件,继续执行。
所以t1并不会一直占用CPU运行,它会主动进入等待状态,让t2有机会执行。主线程在t1.join()处阻塞,只是等待t1完成所有10次循环并退出,而t1的执行过程中会多次暂停,给t2留出运行时间——这完全符合join()的定义,并不矛盾。
第二段代码无死锁的原因
这段代码用条件变量+互斥锁实现了严格的交替执行:
- 初始状态:
first = true,t1的cv1.wait()条件满足,会先执行输出,然后将first设为false,通知t2的cv2。 - 交替触发:
t2被唤醒后,检查!first条件满足,执行输出,将first设为true,通知t1的cv1。 - 循环结束:当
t1完成10次循环后退出,主线程的t1.join()解除阻塞,执行cout << "3\n",然后调用t2.join()等待t2完成剩余的循环(此时t2大概率已经完成了最后一次执行,因为t1最后一次执行后first为false,t2会完成第10次输出)。
整个过程中,互斥锁m的获取和释放是严格成对的:每次wait()会自动释放锁,唤醒后重新获取锁;线程执行完输出后会主动解锁并通知对方。不存在任何线程持有锁不释放、或者互相等待对方锁的情况,因此不会发生死锁。
内容的提问来源于stack exchange,提问作者Athos




