如何更好地解释“僵局”?

时间:2010-01-27 01:01:36

标签: multithreading deadlock concurrency concurrent-programming

我正在努力用简单的词语解释线程中的“死锁”,所以请帮忙。什么可能是“死锁”(例如,在Java中)的最佳示例,以及它如何在步骤中发生以及如何防止它?但是没有深入细节。我知道这就像问两个相反的事情,但仍然。如果您有任何以前的并发编程培训经验 - 那将是一流的!

15 个答案:

答案 0 :(得分:93)

杰克和吉尔碰巧想要同时做三明治。两者都需要一片面包,所以他们都去拿一条面包和一把刀。

杰克首先得到了刀,而吉尔先得到了一条面包。现在杰克试图找到一条面包,而吉尔试图找到这把刀,但两人都发现他们完成任务所需的东西已经在使用。如果他们都决定等到他们需要的东西不再使用,他们将永远等待对方。死锁。

答案 1 :(得分:12)

最简单的方法是让两个不同的线程尝试以不同的顺序获得两个锁:

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.)。

答案 2 :(得分: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锁定B,做一些东西并被中断 线程1尝试锁定B,但线程2已锁定B,因此线程1等待,并被中断 线程2尝试锁定A,但线程1锁定A,因此线程2必须等待。

两个线程都在等待另一个线程释放对他们试图锁定的资源的锁定...

死锁

答案 3 :(得分:5)

我宁愿用与计算机完全无关的术语解释它,因为这通常是获得想法的最佳方式。

我有一个五岁的儿子和一个三岁的女儿。两人都想做同样的着色书。

当儿子抓住书时,女儿抓住铅笔。在他们得到对方之前,他们都不会放弃他们所拥有的东西。

那是僵局。它没有比这简单。

您的进程(或子进程)等待彼此等待,并将继续无限期等待,直到其他一些高级进程(如Dad)进入并打破僵局。

至少对孩子们来说,你可以(有时)让其中一个看到原因并放弃他们的锁定。这通常不适用于计算机,因为除了等待该资源之外,进程没有执行任何(尽管有时子进入此状态)。

遵循一条规则将保证不会发生死锁:

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

遵循一些额外的规则会使你的线程减少相互减速的可能性,但请记住,上述规则应该优先于其他规则:

  • 仅在您需要时分配资源。
  • 完成后立即释放它们。

答案 4 :(得分:1)

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

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

此处的优点是,您可以使用SQL Management Studio进行演示。我过去曾用这个来解释人们在“SQL Server简介”级别培训课程中遇到的僵局。

大多数参与者都对这个理论有困难,但是当他们看到这个理论时,所有人(通常)都会变得清晰。

简而言之:事务A(尚未完成)采用显式表锁。第二个事务B尝试从事务A锁定的表中读取。事务B在事务A提交或回滚之前一直处于死锁状态。

您可以通过创建两个单独的线程来相当容易地在代码中解释这一点,而这些线程又创建了事务。希望它有所帮助。

答案 5 :(得分:1)

通常,并发编程的类通过示例来解释死锁。我认为Dining Philosophers的问题将是一个很好的例子。你可以用Java开发这个例子,并解释当两位哲学家持有左派并正在等待正确的分叉时死锁的发生。 (反之亦然)。

我使用在Java上实现的这些示例从并发编程中学到了很多概念。

答案 6 :(得分:0)

死锁是指两个线程彼此等待,两个线程都不能继续,直到另一个线程先行,所以两者都被卡住了。

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

线程1具有锁定A并且想要锁定B,因此等待锁定B被释放。

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

现在你遇到了僵局。两个线程都在等待锁定,因此两者都没有执行,因此两者都不能释放另一个等待的锁。

答案 7 :(得分:0)

当您有2个不同的资源需要锁定以便使用它们时,会发生死锁。线程以相反的顺序锁定它们,因此直到其中一个线程退出时才能继续执行。

维基百科有couple good real-life examples的死锁。

答案 8 :(得分:0)

当一名工人被另一名工人阻止时,会发生锁链。 A因B而无法继续。链可能更长:A被B阻塞,B被C阻塞,C被D阻塞。

死锁是锁链形成循环的时候。 A被B阻挡,B被C阻断,C被A阻断,链形成了一个环,没有进展。

防止死锁的典型方法是使用锁定层次结构:如果每个工作人员始终以相同的顺序获取锁定,则死锁是不可能的,因为每个阻塞发生在工作者之间,而不是保持锁定排名为X并等待排名的资源Y,其中X> Y总是。在这种情况下,循环不能形成,因为它需要至少一个工作者违反层次结构才能关闭循环。理论如何,至少。在procuice中很难想出现实的层次结构(并且没有,资源的地址不起作用)。

如果无法避免死锁(例如数据库系统),那么解决方案是使用专用线程来检查死锁链以搜索循环并杀死其中一个参与者以释放循环。

答案 9 :(得分:0)

(略微过度简化)有两个人,将螺母拧到螺栓上。

程序(两者相同)是:

  1. 拿起螺母或螺栓
  2. 拿起螺栓或螺母(以你没有的为准)
  3. 将螺母拧到螺栓上
  4. 将成品组件放入“成品”堆中。
  5. 如果仍有螺母和螺栓,请转到步骤1
  6. 那么当只剩下螺母和螺栓时会发生什么?第一个人拿起一个坚果,第二个人拿起一个螺栓。到目前为止一直很好,但现在它们被卡住了,每个都有其他需要的资源。

    如果没有特别说明,他们将永远陷入僵局。

    或者您可以向他们展示this video

答案 10 :(得分:0)

餐饮哲学家 - 你有4个人坐在一张桌子旁,还有4个筷子。你需要2根筷子吃。想象一下,每个哲学家都试图吃如下:

  1. 拿起左筷子。
  2. 拿起正确的筷子。
  3. 吃。
  4. 放回右筷子。
  5. 将左筷子放回原位。
  6. 每个人都做第1步。现在第2步是不可能的,因为每个人都在等待右边的那个人放弃左边,这是他们不会做的。这是僵局。如果他们只是轮流,那么他们都可以吃,但他们都饿死了。

答案 11 :(得分:0)

Guffa的描述很好。

我发现避免死锁的最好方法是只锁定您私有的资源,并在调用任何您无法独占控制的内容之前解除锁定。

唯一的问题是,这可能需要您从使用锁来保持一致性到使用补偿操作,但从长远来看它可能不太可能导致问题。

This article很高兴看到这个问题。

答案 12 :(得分:0)

想象一下你和你的女朋友争吵谁应该打开门才能离开家。道歉的人会打开门。她正在等你道歉,你正在等她道歉,导致这对夫妇从不离开家,因为两人都拒绝道歉。

答案 13 :(得分:0)

想象一个犯罪分子挟持人质并索要赎金。你出现了一个装满钱的手提箱。

罪犯在得到钱之前绝不会释放人质。在你获得人质之前,你永远不会放钱。死锁。

这里的类比是:

  • 罪犯主题
  • 装满钱的行李箱人质资源

答案 14 :(得分:0)

示例1:

我保证永远不会做承诺

示例2:

与灯的精灵交谈:我的愿望是你永远不会实现愿望

示例3:

招聘人员:如果您解释僵局,我会雇用您。

候选人:如果您雇用我,我将解释什么是僵局