请解释一下锁的顺序问题?

39

我了解到应该先解锁逆序,然后才能锁定顺序。例如:

A.lock();
B.lock();
B.unlock();
A.unlock();

但是,如果我像这样做会发生什么:

A.lock();
B.lock();
A.unlock();
B.unlock();

我试图制造一个死锁场景,但如果我总是先锁定A,然后才锁定B,那么我不知道死锁会发生在哪里。你能帮我吗?


你是在谈论多个进程持有锁的情况还是仅处理单个进程? - Mahesh Velaga
多个进程,当然。 - P-P
6
当然不是,为什么要使用多个进程?同样适用于单个进程内的线程。 - djna
1
http://yarchive.net/comp/linux/lock_ordering.html - Thilo
7个回答

66
在这个简单的例子中,为了避免死锁,不需要按相反的顺序解锁。
然而,随着代码变得更加复杂,按相反的顺序解锁有助于您维护适当的锁顺序。
考虑:
A.lock();
B.lock();
Foo();
A.unlock();
Bar();
B.unlock();
如果Bar()试图重新获取 A,那么你已经打破了锁的顺序。你持有 B 并尝试获取 A。此时可能会发生死锁。
如果您使用 RAII(资源获取即初始化)方式实现解锁反向排序(非常自然的方式):
A.lock();
B.lock();
Foo();
B.unlock();
Bar();
A.unlock();

那么如果 Bar() 试图获取锁,锁定顺序将得以保留,这时就无所谓了。


1
这不仅是正确的答案,而且它指出了所有声称解锁顺序与死锁无关的答案都是错误的。 - Warren Dew
1
那个问题只会在您重新获取已经释放的锁(而没有首先释放“后面”的锁)时发生。我所谓的错误答案将锁定排序定义为“按固定顺序获取锁定,在开始解锁后不再获取锁定”。 - Thilo
2
引用总是雄辩的Linus Torvalds在这个问题上的话:“事实是,解锁不必嵌套得干净利落。这是一个非常重要的事实。锁定顺序很重要,而解锁顺序则不重要。如果你不能接受这个事实,并且说“为了保持事情的整洁,解锁顺序应该很重要”,那么当你无法遵守解锁顺序时,你最终会陷入困境和/或混乱。有许多完全有效的理由不按相反的顺序解锁。”http://yarchive.net/comp/linux/lock_ordering.html - Thilo
3
我并未声称其他人的答案是错误的,也没有说你必须按相反的顺序解锁。我指出,按相反的顺序解锁可以更容易地确保在代码发展过程中始终保持锁定顺序。 - Adrian McCarthy
1
我认为第二种情况不是一个好的例子,因为你说“如果Bar()再次尝试锁定A,那么锁定顺序将得到保留,这并不重要”,但是如果你尝试在已经锁定A的范围内锁定A,那么程序将会挂起。剩下的答案是好的(即第一种情况),关于死锁的评论非常相关。我建议编辑第二个案例并替换代码,使用一个相反的顺序来展示死锁实例,以表明这里的重点不在于锁定顺序(也许与 @don-neufeld 给出的示例相同)。 - corporateAbaper
显示剩余8条评论

31

锁定顺序仅意味着您按照固定顺序获取锁,并在开始解锁后不再获取锁,以防止死锁。

我认为在这里解锁的顺序并不重要(事实上,尽早释放锁甚至是无序的应该是有益的)。


13

你的示例永远不会发生死锁。按相反的顺序解锁并不重要,重要的是以一致的顺序进行锁定。即使解锁是按相反的顺序进行的,这也会导致死锁。

Thread 1

A.lock();
B.lock();
B.unlock();
A.unlock();

Thread 2

B.lock();
A.lock();
A.unlock();
B.unlock();

2

我不认为这里会发生死锁。一般的死锁概念是一个线程等待其他线程锁定的某些资源,而另一个线程需要第一个线程锁定的资源才能完成并释放第一个线程所需的资源。

更多阅读


2
解锁的顺序不会影响系统死锁的倾向性,然而有一个原因需要考虑解锁的顺序:
为了避免死锁,你必须确保你的锁定/解锁是成对出现的,即你从不错过解锁。作为一种风格上的方法,通过明显地拥有负责特定锁定的代码块,更容易视觉上识别锁定和解锁是成对出现的。最终的效果是,正确的代码可能会按照你描述的方式获取和释放锁定。

0
      > lock(B)                                                                                                                                                                                                 
      >  ----------    lock(C)
      >  ----------    lock(B)    >>>> would block here 
      >  ----------    release(B)
      >  ----------    release(C)
      > lock(C)       >>>>>> would block here 
      > release(B)
      > release(C)

他们的答复非常棒,下面是另一种情况:如果执行无序的锁定和释放,死锁可能会发生。简言之,无序的释放和锁定 打破了我们用来设计共享资源管理和关键区域的假设。


0
对于Java而言,如果使用synchronized关键字进行锁定,则解锁的顺序是相反的。使用synchronized关键字进行锁定后,无法以不同的顺序进行解锁。
synchronized(a) {
  synchronized(b) {
    // statements
  }
}

3
我想表达的是,我不同意对这个回答的踩票。虽然它不是关于这个问题最复杂的回答,但它展示了另一种观点,或许有助于理解这个问题。 - Wampie Driessen

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