我有这个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
,我会发现死锁,但代码只是执行。任何好的解释。
答案 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模式更便宜,并使其真正苗条。