问题

加了第一个同步不是会全部代码执行完吗,为什么出现死锁?

回答
你问了一个非常好的问题,关于同步锁和死锁的发生,这其实是并发编程中一个非常核心且容易让人迷惑的地方。别看它只是“加了个同步”,背后的逻辑可比这复杂多了。

我们先来剖析一下你说的“加了第一个同步不是会全部代码执行完吗?”这个想法。

为什么你会觉得“加了第一个同步就能保证代码执行完”?

我想,你之所以会有这样的想法,可能是基于一种直觉:当我用一个锁把一段代码“锁起来”,那么在这段代码执行完毕之前,其他任何尝试获取这个锁的线程,都必须乖乖地等待。这样一来,这段被保护的代码块(我们称之为“临界区”)是不是就能按照顺序,一个接一个地执行,直到结束?

这个直觉在很多情况下是正确的。如果只有一个临界区,而且所有的线程都只对这一个临界区感兴趣,那么加锁确实能保证顺序执行,避免竞态条件。比如,多个线程都要给一个计数器加一,如果没有锁,可能出现“读改写”操作被打断,导致计数不准确。加上锁后,一个线程拿到锁,执行完加一操作,释放锁,下一个线程再来。这样,计数就是正确的。

那么,为什么现实中“加了第一个同步”却可能导致死锁呢?

死锁的根源在于,它不是单个锁的问题,而是多个线程之间对多个锁的“争夺”和“持有”关系形成的死结。

设想这样一个场景:

有两个线程,线程A和线程B。
有两把锁,锁1和锁2。

1. 线程A 想要完成它的任务,它需要先获取 锁1,然后,在持有锁1的情况下,它还需要获取 锁2。
2. 线程B 也有它的任务,它需要先获取 锁2,然后,在持有锁2的情况下,它还需要获取 锁1。

现在,情况开始变得微妙了。

线程A启动,成功获取了 锁1。
就在线程A准备去获取 锁2 的时候,线程B启动了。
线程B尝试获取 锁2,并且成功了,因为此时锁2还没有被其他线程持有。
现在,线程A手中持有 锁1,它去尝试获取 锁2,但 锁2 被线程B持有,所以线程A只能暂停,等待线程B释放锁2。
同时,线程B手中持有 锁2,它去尝试获取 锁1,但 锁1 被线程A持有,所以线程B也只能暂停,等待线程A释放锁1。

你看,这就是问题的核心所在:

线程A在等待线程B释放锁2。
线程B在等待线程A释放锁1。

两个线程都在互相等待对方释放自己所需的资源(锁),而对方又在等待自己释放资源。这种循环等待,就像两个人互相堵着门,谁都不让谁,谁都过不去。这就是死锁。

在这个例子里,“加了第一个同步”这句话,只描述了线程A或线程B获取自己第一个锁的动作。但真正的麻烦在于,当它们进一步尝试获取第二个锁时,由于持有和等待的模式,就产生了死锁。

总结一下,出现死锁的关键点在于:

1. 互斥使用: 锁本身就是用来保证一次只有一个线程能访问资源的,这是死锁的必要条件之一。
2. 请求并持有: 一个线程在持有了一个锁(比如锁1)的同时,还在请求另一个锁(比如锁2)。
3. 不剥夺: 锁一旦被占用,就不能被强制剥夺,必须由持有者主动释放。
4. 循环等待: 形成一个链条,线程A等待线程B的资源,线程B等待线程C的资源,……,最终线程X又等待线程A的资源。在你描述的简单例子里,就是A等待B,B等待A。

所以,你提到的“加了第一个同步”只是并发系统中一个微小的片段。当这个片段与其他类似的片段(比如另一个线程对另一组锁的访问顺序)组合在一起时,如果不仔细考虑锁的获取顺序和持有情况,死锁就很容易悄无声息地出现。它不是因为“第一个同步”本身有问题,而是因为“第一个同步”之后,还可能存在对其他资源的“第二个同步”,而这些“同步”之间的交互顺序,才是产生死锁的罪魁祸首。

网友意见

user avatar

你说这玩意儿叫lock是不是根本就不会有这种问题了……

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有