You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C++线程join()工作原理及代码无死锁原因咨询

解答:两段线程同步代码的死锁疑问与std::thread::join()机制解析

一、第一段代码为何不会死锁?

先梳理这段代码的执行流程和锁的交互逻辑:

  1. 初始状态:主线程main首先调用lock_bar_.lock(),独占了lock_bar_锁。
  2. 线程启动:启动t1(执行foo)和t2(执行bar):
    • t2进入bar函数,第一行就是lock_bar_.lock(),但lock_bar_已经被主线程持有,所以t2立即阻塞,等待锁释放。
    • t1进入foo函数,调用lock_foo_.lock()(此时lock_foo_无人持有,成功获取),然后执行输出,接着调用lock_bar_.unlock()——这一步释放了主线程持有的lock_bar_锁。
  3. 线程交替执行
    • lock_bar_被释放后,阻塞的t2立即获取到锁,执行输出,然后调用lock_foo_.unlock()释放lock_foo_
    • t1的下一次循环又能获取lock_foo_,重复上述流程:输出→释放lock_bar_,让t2继续执行。

死锁的发生需要满足四个必要条件:互斥访问、持有并等待、不可剥夺、循环等待。这段代码完全不满足「循环等待」和「持有并等待」:

  • 每个线程不会同时持有两个锁foo只持有lock_foo_,释放的是lock_bar_bar只持有lock_bar_,释放的是lock_foo_
  • 锁的获取没有形成循环依赖:线程之间不存在“我拿着你要的锁,你拿着我要的锁”的情况,始终是单向的锁释放触发下一个线程的执行。

正因为如此,这段代码只会让t1t2交替执行,绝不会出现死锁。

二、std::thread::join()的实际工作机制与第二段代码无死锁原因

关于std::thread::join()的误解澄清

你看到的cppreference描述完全准确:join()阻塞调用它的线程(这里是主线程),直到目标线程(t1)完全执行完毕。但你觉得“实际运行现象不符”,大概率是误解了「阻塞主线程」的含义——它只是让主线程暂停执行后续代码,并不会阻止其他线程(比如t2)的运行

在第二段代码中,t1的执行流程是:

  • 每次循环获取锁后,会调用cv1.wait(ul, [&](){return first;}):如果firstfalsewait()会释放锁并让t1进入阻塞状态,等待条件变量的通知。
  • t2执行完输出并调用cv1.notify_all()后,t1才会被唤醒,重新检查条件,继续执行。

所以t1并不会一直占用CPU运行,它会主动进入等待状态,让t2有机会执行。主线程在t1.join()处阻塞,只是等待t1完成所有10次循环并退出,而t1的执行过程中会多次暂停,给t2留出运行时间——这完全符合join()的定义,并不矛盾。

第二段代码无死锁的原因

这段代码用条件变量+互斥锁实现了严格的交替执行:

  1. 初始状态first = truet1cv1.wait()条件满足,会先执行输出,然后将first设为false,通知t2cv2
  2. 交替触发t2被唤醒后,检查!first条件满足,执行输出,将first设为true,通知t1cv1
  3. 循环结束:当t1完成10次循环后退出,主线程的t1.join()解除阻塞,执行cout << "3\n",然后调用t2.join()等待t2完成剩余的循环(此时t2大概率已经完成了最后一次执行,因为t1最后一次执行后firstfalset2会完成第10次输出)。

整个过程中,互斥锁m的获取和释放是严格成对的:每次wait()会自动释放锁,唤醒后重新获取锁;线程执行完输出后会主动解锁并通知对方。不存在任何线程持有锁不释放、或者互相等待对方锁的情况,因此不会发生死锁。

内容的提问来源于stack exchange,提问作者Athos

火山引擎 最新活动