【问题标题】:How to explain the "deadlock" better?如何更好地解释“僵局”?
【发布时间】:2011-01-09 18:36:00
【问题描述】:

我正在努力用简单的话解释线程中的“死锁”,所以请帮忙。什么是“死锁”(例如,在 Java 中)的最佳示例,它是如何分步发生的以及如何防止它?但没有深入细节。我知道这就像问两个相反的事情,但仍然如此。如果你之前有任何并发​​编程培训经验——那就太棒了!

【问题讨论】:

标签: multithreading deadlock concurrency concurrent-programming


【解决方案1】:

样本 1:

我保证永远不会做承诺

样本 2:

与灯神对话:我的愿望是你永远不会实现愿望

示例 3:

招聘人员:如果你解释死锁,我会雇用你。

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 理解这个概念的最佳例子..:)
    • 这应该是死锁的正式定义。简单、容易、整洁、干净,是我见过的最好的例子
    • 为什么杰克不能从吉尔那里得到面包,做他的工作,然后把剩下的面包给吉尔,让他也可以做​​他的工作?我的意思是在计算上这是一个无法解决的问题吗?
    • @Nishant:是的,可以协商资源,但这不是微不足道的。基本上,杰克需要约翰来监督杰克资源的使用(而吉尔需要简),并在杰克忙的时候回答诸如“杰克现在真的在使用它吗”之类的问题。
    【解决方案3】:

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

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


    这里的比喻是:

    • 罪犯线索
    • 装满钱的手提箱人质资源

    【讨论】:

      【解决方案4】:

      想象一下,你和你的女朋友为了离开家应该开门而吵架。道歉的人会开门。她在等你道歉,你在等她道歉,这导致这对夫妇永远不会离开家,因为他们都拒绝道歉。

      【讨论】:

        【解决方案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 必须等待。

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

        死锁

        【讨论】:

          【解决方案6】:

          Guffa 的描述很好。

          我发现避免死锁的最佳方法是仅锁定您私有的资源,并在调用您没有独占控制权的任何内容之前释放锁定。

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

          This article 很高兴了解这个问题。

          【讨论】:

            【解决方案7】:

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

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

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

            大多数参与者对理论有疑问,但是当他们看到它在行动时,这一切(通常)都会变得清晰。

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

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

            【讨论】:

              【解决方案8】:

              通常并发编程的类通过例子来解释死锁。我认为Dining Philosophers 的问题将是一个很好的例子。您可以使用 Java 开发此示例,并解释当两个哲学家拿着左叉并等待右叉时发生死锁。 (反之亦然)。

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

              【讨论】:

                【解决方案9】:

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

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

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

                这是僵局。没有比这更简单的了。

                您的进程(或子进程)相互等待,并将继续无限期地等待,直到其他高级进程(如爸爸)进入并打破僵局。

                至少对于孩子,您可以(有时)让其中一个看到原因并放弃他们的锁定。这在计算机上通常是不可能的,因为进程除了等待该资源外,什么都不做任何事情(尽管有时孩子也会进入这种状态)。

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

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

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

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

                【讨论】:

                • 非常低估的答案。这抓住了原始答案的精髓,还解释了它是什么以及它是这样的原因。优秀。但是,当您说“至少与孩子一起,您可以(有时)让其中一个看到原因”,这可以通过编程方式完成吗?像while循环或我不知道的东西......只是一个新手问题。
                • @Nishant,除非线程正在使用类似定时 trylock 的东西,否则不会。使用“真正的”锁类似于孩子将手指伸入耳朵并大声说“la la la la la ...”:-) 有时有一些方法可以强制执行线程释放锁,但这可能会使它保护的数据处于不一致的状态。更好地设计你的程序,使死锁是不可能的。
                【解决方案10】:

                餐饮哲学家 - 你有 4 个人坐在一张桌子旁,还有 4 支筷子。你需要两根筷子才能吃。想象一下每个哲学家都尝试如下吃东西:

                1. 拿起左筷子。
                2. 拿起右边的筷子。
                3. 吃。
                4. 把右筷子放回去。
                5. 把左筷子放回去。

                每个人都执行第 1 步。现在第 2 步是不可能的,因为每个人都在等待右边的人放弃左边的人,而他们不会这样做。这是僵局。如果他们只是轮流,那么他们都可以吃东西,但他们都饿死了。

                【讨论】:

                  【解决方案11】:

                  (稍微过分简化)有两个人,正在将螺母拧到螺栓上。

                  程序(两者相同)是:

                  1. 拿起一个螺母或螺栓
                  2. 拿起一个螺栓或一个螺母(你还没有)
                  3. 将螺母拧到螺栓上
                  4. 将完成的组件放入“完成”堆中。
                  5. 如果螺母和螺栓仍然存在,请转到步骤 1

                  那么当只剩下一个螺母和一个螺栓时会发生什么?第一个人拿起一个螺母,第二个人抓住一个螺栓。到目前为止一切顺利,但现在他们陷入了困境,每个人都有其他人需要的资源。

                  如果没有特别的指示,他们将永远僵在那里。

                  或者你可以告诉他们this video

                  【讨论】:

                    【解决方案12】:

                    当一个工作人员被另一个工作人员阻塞时,就会发生锁链。 A因为B不能继续。链可以更长:A被B挡住,B被C挡住,C被D挡住。

                    死锁是指锁链形成循环。 A被B堵住,B被C堵住,C被A堵住,链条已经形成一个循环,无法前进。

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

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

                    【讨论】:

                      【解决方案13】:

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

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

                      【讨论】:

                        【解决方案14】:

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

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

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

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

                        现在你陷入了僵局。两个线程都在等待一个锁,所以两个线程都没有执行,所以两个线程都不能释放另一个正在等待的锁。

                        【讨论】:

                          【解决方案15】:

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

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

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2012-01-28
                            • 2016-06-21
                            • 1970-01-01
                            • 1970-01-01
                            • 2017-01-07
                            • 2021-08-13
                            • 1970-01-01
                            相关资源
                            最近更新 更多