如何更好地解释“死锁”?

32

我很难用简单的话来解释线程中的“死锁”,所以请帮忙。有没有最好的示例可以说明“死锁”(比如在Java中),它是如何按步骤发生的,如何预防呢?但不要深入细节。我知道这就像是在要求两件相反的事情,但还是请教一下。如果你有任何并发编程的经验 —— 那就太好了!


1
可能是死锁是什么?的重复问题。 - fabian
15个回答

113

杰克和吉尔恰好同时想做一个三明治。两个人都需要一片面包,所以他们都去拿面包和刀。

杰克先拿到了刀,而吉尔先拿到了面包。现在杰克试图找到面包,吉尔试图找到刀,但是他们发现完成任务所需的工具已经在使用中。如果他们决定等待直到所需工具不再在使用中,他们将永远互相等待。死锁。


最好的例子来理解这个概念.. :) - sush
这应该是死锁的正式定义。简单、易懂、简洁明了,是我见过的最好的例子。 - Varun
为什么杰克不能从吉尔那里得到面包,完成他的工作,然后把剩下的面包给吉尔,这样他也可以完成他的工作呢?我的意思是,从计算上来说,这是一个无法解决的问题吗? - Nishant
1
@Nishant:是的,资源可以协商,但这并不容易。基本上,杰克需要约翰监督杰克的资源使用(吉尔需要简),并回答像“杰克现在真的在使用那个吗”这样的问题,而杰克很忙。 - Guffa

13
最简单的方法是让两个不同的线程尝试以不同的顺序获取两个锁:
thread 1:
lock(a)
lock(b)

thread2:
lock(b)
lock(a)

假设线程1获取锁A并进入睡眠状态。线程2获取锁B并尝试获取锁A;由于锁A已被占用,线程2将等待直到锁A被解锁。现在线程1重新唤醒并尝试获取锁B,也会进入睡眠状态。
针对这种情况,有几种方法可以防止它发生:
  1. 一个线程不应同时持有两个锁。
  2. 如果必须同时持有两个锁,则必须始终按相同的顺序获取它们(因此在上面的例子中,线程2需要修改为在请求锁B之前请求锁A)。

5
  Thrd 1 --- Lock A        - atmpt lock on B -   
         \                /                   \
          \              /                     \           
           \            /                       \         
            --- Lock A /                         --- wait for lock on B

  Thrd 2--- Lock B         - atmpt lock on A -   
         \                /                   \
          \              /                     \           
           \            /                       \         
            --- Lock B /                         --- wait for lock on A

线程1开始运行,锁定A,执行一些操作,然后被线程2中断,线程2锁定B,执行一些操作并被中断,线程1尝试锁定B,但线程2已经锁定了B,所以线程1等待,并被线程2中断,线程2尝试锁定A,但线程1已经锁定了A,所以线程2也必须等待。

两个线程都在等待另一个线程释放它们正在尝试获取锁定的资源...

死锁


5

我更愿意用与电脑完全无关的术语来解释,因为这通常是传达思想的最佳方式。

我有一个五岁的儿子和一个三岁的女儿。两个孩子都想做同一本涂色书。

女儿拿起了彩色铅笔,而儿子拿起了涂色书。他们都不会放弃自己手里的东西,直到得到对方的东西。

这就是僵局。再简单不过了。

你的进程(或孩子)被卡在等待对方的状态中,并将继续无限期地等待,直到其他上级进程(如父亲)介入并打破僵局。

至少对于孩子们,你可以(有时)让其中一个理性地放弃他们的锁。但是这通常在计算机上是不可能的,因为进程除了等待资源外什么都不做(尽管有时候孩子也会进入这种状态)。

遵循一个规则将确保死锁不会发生:

  • 所有执行线程按相同的顺序分配资源。

遵循一些额外的规则将使您的线程不太可能相互减速,但请记住,上述规则应优先于所有其他规则:

  • 只有在需要时才分配资源。
  • 完成后立即释放它们。

非常被低估的答案。它捕捉了原始答案的本质,同时还解释了它是什么以及为什么会这样。非常好。然而,当你说“至少对于孩子们来说,你可以(有时)让他们中的一个理性地看待问题”,这是否可以通过编程实现?像while循环之类的东西,我不知道...只是一个新手问题。 - Nishant
1
@Nishant,除非线程使用类似定时尝试锁定的东西,否则不行。使用“真正”的锁就像孩子把手指塞进耳朵里大声喊“la la la la la…” :-) 有时可以强制执行线程以释放锁定,但这可能会使其保护的数据处于不一致状态。更好的方法是设计程序,使死锁变得不可能。 - paxdiablo

1

另一种演示死锁的好方法是使用SQL Server。

使用不同隔离级别的事务,您可以演示一个事务将无限期地等待被另一个事务锁定的表。

这里的优点是,您可以使用SQL Management Studio进行演示。我过去曾在教授“介绍SQL Server”级别的培训课程时向人们解释死锁。

大多数参与者都对理论感到困惑,但当他们看到它实际发生时,一切(通常)就变得清晰了。

简而言之:未完成的事务A(具有显式表锁定)获取了一个显式表锁定。第二个事务B尝试从被事务A锁定的表中读取。事务B被死锁,直到事务A提交或回滚。

您可以通过创建两个单独的线程来轻松地在代码中解释这一点,这两个线程依次创建事务。希望能有所帮助。


1
通常并发编程的课程会通过例子来解释死锁。我认为餐桌上的哲学家问题是一个很好的例子。你可以用Java开发这个例子,并解释当两个哲学家拿着左边的叉子等待右边的叉子(或反之亦然)时死锁的发生。
我通过在Java上实现这些例子学到了很多并发编程的概念。

0

想象一下,你和女友因为谁应该打开门离开房子而争吵。道歉的人将会打开门。她在等你道歉,你在等她道歉,结果这对夫妇永远不会离开房子,因为两个人都拒绝道歉。


0

想象一下,一个罪犯劫持了人质并要求赎金。你带着满满一箱钱出现了。

在罪犯得到钱之前,他永远不会释放人质。在你得到人质之前,你永远不会放出钱。僵局。


这里的类比关系是:

  • 罪犯线程
  • 满载钞票的手提箱人质资源

0

死锁是指两个线程相互等待,彼此都无法继续执行,直到另一个线程先执行完毕,因此两个线程都被卡住了。

死锁需要至少两个锁,并且两个线程都必须包含获取锁并等待释放锁的代码。

线程1持有锁A并想要锁B,所以它等待锁B被释放。

线程2持有锁B并想要锁A,所以它等待锁A被释放。

现在你有了一个死锁。两个线程都在等待锁,因此都没有在执行,所以两个线程都无法释放对方正在等待的锁。


0

死锁发生在你有两个不同的资源,两个不同的线程需要锁定它们才能使用。这些线程以相反的顺序锁定它们,因此执行变得不可能,直到其中一个线程放弃。

维基百科有一些很好的现实生活例子可以说明死锁。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接