C#中的可重入锁

132

在 .NET 的 C# 中,以下代码是否会导致死锁?

 class MyClass
 {
    private object lockObj = new object();

    public void Foo()
    {
        lock(lockObj)
        { 
             Bar();
        }
    }

    public void Bar()
    {
        lock(lockObj)
        { 
          // Do something 
        }
    }       
 }

7
我们是否可以考虑更改这个问题的标题 - 也许改为类似于最近关闭的嵌套锁为什么不会导致死锁?。现有标题似乎几乎是为了防止人们发现它而设计的。 - Jeff Sternal
13
实际上,我是基于搜索词“可重入”找到了这个答案,并且回答了我的问题。如果这是一个重复的问题,那就是另外一个问题了... - emfurry
我同意@JeffSternal的评论,这个问题假设正在寻找问题的人已经熟悉“可重入”锁。我认为另一个重复的问题对此有一个好标题:https://dev59.com/aHA65IYBdhLWcg3wrgsa - Luis Perez
4个回答

167
不会,只要你锁定相同的对象,就不会出现问题。 递归代码实际上已经拥有了锁,因此可以继续无障碍运行。
“lock(object){...}”是使用Monitor类的简写。正如Marc指出的那样, Monitor 允许重入性,因此在一个当前线程已经拥有锁的对象上重复尝试锁定将完全正常工作。
如果您开始锁定不同的对象,那么就必须小心。 特别注意以下事项:
  • 始终以相同的顺序获取给定数量的对象的锁定。
  • 始终以与获取它们的顺序相反的顺序释放锁定。
如果违反这些规则,则几乎可以保证在某个时候会出现死锁问题。
这是一个很好的描述.NET中线程同步的网页:http://dotnetdebug.net/2005/07/20/monitor-class-avoiding-deadlocks/ 另外,尽可能少地锁定对象。在可能的情况下考虑应用粗粒度锁。其想法是,如果您可以编写代码以便有一个对象图,并且您可以在该对象图的根上获取锁,则请这样做。这意味着您在该根对象上有一个锁,因此不必过于担心获取/释放锁的顺序。
(另外一点,您的示例技术上并不是递归的。要使其成为递归,Bar()通常会作为迭代的一部分调用自身。)

1
特别是在不同的序列中。 - Marc Gravell
6
递归;确实;为了盖伊的利益,术语是可重入的。 - Marc Gravell
感谢对术语的澄清 - 我已经编辑并纠正了问题。 - Guy
这个问题似乎引起了相当多的关注,所以我已经更新了我的答案,并加上了一些自从我第一次写下它以来想到的其他注意事项。 - Neil Barnwell
1
实际上,我认为释放锁的顺序并不重要。你选择它们的顺序肯定很重要,但只要释放锁不受任何条件限制(你可以随时释放),只要你释放了所有已获取的锁,就应该没问题。 - bobroxsox
@bobroxsox 是的,只要你只是释放而不执行任何其他操作,比如获取另一个锁。 - AyCe

22

嗯,监视器允许重入,因此您不会死锁自己...所以不应该出现死锁。


8

如果一个线程已经持有锁,那么它不会阻塞自己。.Net框架确保了这一点。您只需要确保两个线程不会通过任何代码路径尝试以不同的顺序获取相同的两个锁。

同一线程可以多次获取同一锁,但您必须确保释放锁的次数与获取锁的次数相同。当然,只要您使用“lock”关键字来实现这一点,这将自动发生。


请注意,这适用于监视器,但不一定适用于其他类型的锁。 - Jon Skeet
不是要暗示你不知道这一点,当然 - 只是这是一个重要的区别 :) - Jon Skeet
这是一个很好的观点。我实际上打算在整个代码中将"lock"改为"monitor",但是后来分心了。而且有些懒。而且这种行为对于Windows互斥内核对象也是适用的,所以我想,差不多就行了! - Jeffrey L Whitledge

5
不,这段代码不会出现死锁。 如果你真的想创建死锁,最简单的情况需要至少2个资源。 考虑狗和骨头的场景。
1. 一只狗完全控制着一个骨头,所以其他狗必须等待。 2. 至少需要两只狗和两个骨头才能创建死锁,它们分别锁定自己的骨头并试图获取对方的骨头。
...以此类推,n只狗和m个骨头可以引发更复杂的死锁。

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