3

我有这个 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但代码只是执行,我预计会出现死锁。任何好的解释。

4

3 回答 3

13

lock阻塞正在执行的线程,除非该线程已经持有对象上的锁。

在这种情况下,只有一个线程在执行;它在lockValuein上锁定GetInt2,然后进入GetInt1,在那里它再次遇到一个 lock 语句lockValue- 它已经持有,所以它被允许继续。

于 2013-06-18T10:07:58.063 回答
7

C# 中的lock语句是语法糖,编译器将其解释为对Monitor.Enter. 记录在案(在“监视器”部分)

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但是,在等待对象的其他线程解除阻塞之前,必须调用相同数量的调用。

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

于 2013-06-18T10:10:17.107 回答
5

这里的一般情况是同步对象是否是可重入的。换句话说,如果它已经拥有锁,则可以被同一个线程再次获取。另一种说法是对象是否具有“线程亲和力”。

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

使同步对象可重入需要额外的成本,它需要跟踪哪个线程拥有它以及在拥有线程上获取锁的频率。这需要存储 Thread.ManagedId 和一个计数器,两个整数。这影响了 C++ 中的选择,例如,C++11 语言规范最终将线程添加到标准库中。std::mutex 类在该语言中不可重入,添加递归版本的提议被拒绝。他们认为让它重入的开销太高了。可能有点笨拙,相对于调试意外死锁所花费的时间而言,成本相当微不足道:) 但它是一种语言,可以保证获取线程 ID 的成本很便宜,就像它在 .网。

这在 ReaderWriterLockSlim 类中公开,您可以选择。请注意 RecursionPolicy 属性,它允许您在 NoRecursion 和 SupportsRecursion 之间进行选择。NoRecursion 模式更便宜,而且非常苗条

于 2013-06-18T11:02:04.817 回答