为什么这段代码不会以死锁结束

时间:2013-06-18 10:05:30

标签: c# .net c#-4.0 locking

我有这个C#代码:

public class Locking
{

    private int Value1; private int Value2;

    private object lockValue = new Object();
    public int GetInt1(int value1, int value2)
    {
        lock (lockValue)
        {
            Value1 = value1;
            Value2 = value2;
            return GetResult();
        }

    }

    public int GetInt2(int value1, int value2)
    {
        lock (lockValue)
        {
            return GetInt1(value1, value2);
        }
    }

    private int GetResult()
    {
        return Value1 + Value2;
    }


}

所以基本上,如果我执行GetInt2,我会发现死锁,但代码只是执行。任何好的解释。

3 个答案:

答案 0 :(得分:12)

lock阻止执行线程,除非该线程已经持有对象的锁。

在这种情况下,只有一个线程在执行;它会锁定lockValue中的GetInt2,然后进入GetInt1,再次遇到lockValue上的锁定语句 - 它已经存在,因此允许继续。

答案 1 :(得分:7)

C#中的lock语句是语法糖,由编译器解释为对Monitor.Enter的调用。它的documented(在"监视器"部分)

lock (x)
{
    DoSomething();
}

相当于

System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
    DoSomething();
}
finally
{
    System.Threading.Monitor.Exit(obj);
}

Monitor.Enter的文档声明了

  

同一个线程不止一次调用Enter是合法的   没有阻挡;但是,必须进行相同数量的Exit次呼叫   在等待对象的其他线程取消阻塞之前调用。

从上面可以明显看出,只要涉及一个线程,给定的代码就不会产生死锁。

答案 2 :(得分:4)

此处的一般情况是同步对象是否为 re-entrant 。换句话说,如果已经拥有锁,则可以通过相同的线程再次获取。另一种说法是对象是否具有“线程亲和力”。

在.NET中,Monitor类(实现lock语句),Mutex和ReaderWriterLock是可重入的。 Semaphore和SemaphoreSlim类是,你可以使用二进制信号量使你的代码死锁。实现锁定最便宜的方法是使用Interlocked.CompareExchange(),它也不会重入。

使同步对象重入需要额外的成本,它需要跟踪哪个线程拥有它以及在拥有线程上获取锁的频率。这需要存储Thread.ManagedId和一个计数器,两个整数。这影响了C ++中的选择,例如,C ++ 11语言规范最终为标准库添加了线程。 std :: mutex类在该语言中可重入,并且拒绝添加递归版本的提议。他们考虑了使其重入过高的开销。也许有点笨手笨脚,花费在调试意外死锁上花费的时间相当微不足道的成本:)但它是一种语言,它不是灌篮,获得线程ID可以保证像它一样便宜。 NET。

这在ReaderWriterLockSlim类中公开,您可以选择。请注意RecursionPolicy属性,允许您在NoRecursion和SupportsRecursion之间进行选择。 NoRecursion模式更便宜,并使其真正苗条