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

我有这个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但代码只是执行。 任何好的解释。

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

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

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

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

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

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

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调用。

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