根据我的理解,在运行时完成锁(obj)的代码块之前不会释放锁(因为当块完成时,它会调用Monitor.Exit(obj)。
根据这种理解,我无法理解以下代码行为背后的原因:
private static string obj = "";
private static void RecurseSome(int number)
{
Console.WriteLine(number);
lock (obj)
{
RecurseSome(++number);
}
}
// 致电: RecurseSome(0)
// 输出: 0 1 2 3...... stack overflow exception
必须有一些我缺少的概念。请帮忙。
答案 0 :(得分:35)
锁知道哪个线程锁定了它。如果同一个线程再次出现,它只会增加一个计数器并且不会阻塞。
因此,在递归中,第二个调用也进入 - 并且内部锁定会增加锁定计数器 - 因为它是相同的线程(已经存在锁定)。
MS-帮助://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_csref/html/656da1a4-707e-4ef6-9c6e-6d13b646af42.htm
或MSDN:http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx
规定:
lock关键字确保一个线程不会进入代码的关键部分,而另一个线程位于关键部分。如果另一个线程试图输入锁定的代码,它将等待,阻止,直到该对象被释放。
注意线程引用和强调“另一个”线程。
答案 1 :(得分:35)
请执行 NOT 锁定字符串对象。这可能会导致意外行为,例如应用程序中的死锁。您当前正在锁定空字符串,这更糟糕。整个程序集使用相同的空字符串。并使事情变得更糟;作为优化,CLR在AppDomains上重用字符串。锁定字符串意味着您可能正在进行跨域锁定。
使用以下代码作为锁定对象:
private readonly static object obj = new object();
更新
事实上,我认为可以安全地说允许锁定任何东西是.NET框架中的一个主要设计缺陷。相反,他们应该创建某种SyncRoot
密封类,只允许lock
语句和Monitor.Enter
接受SyncRoot
的实例。这会给我们带来很多苦难。我确实知道这个漏洞来自哪里; Java具有相同的设计。
答案 2 :(得分:9)
正如其他人已经指出的那样,锁是由线程进行的,因此可以正常工作。但是,我想在此添加一些内容。
来自Microsoft的并发专家Joe Duffy有多个关于并发的设计规则。他的一个设计规则是:
9。避免在设计中锁定递归。使用非递归锁 在可能的情况下。
递归通常表示您的过度简化 经常同步设计 导致代码不太可靠。一些 设计使用锁递归作为一种方法 避免将功能分解为那些功能 拿锁和那些假设 锁已被采取。这个可以 诚然,导致代码减少 尺寸因此更短 写作时间,但结果更多 脆弱的设计到底。 p>
(source)
要防止递归锁定,请将代码重写为以下内容:
private readonly static object obj = new object();
private static void Some(int number)
{
lock (obj)
{
RecurseSome(number);
}
}
private static void RecurseSome(int number)
{
Console.WriteLine(number);
RecurseSome(++number);
}
此外,我的代码将抛出StackOverflowException
,因为它永远不会以递归方式调用自身。您可以按如下方式重写您的方法:
private static void RecurseSome(int number)
{
Console.WriteLine(number);
if (number < 100)
{
RecurseSome(++number);
}
}
答案 3 :(得分:6)
锁定由当前线程拥有。递归调用也是在当前线程上进行的。如果另一个线程尝试获取锁定,它将阻止。
答案 4 :(得分:1)
如果你问的是堆栈溢出异常 - 那是因为那里没有任何东西可以打破递归。堆栈空间通常只有几K,你可以很快耗尽空间。
现在这种情况下的锁可用于序列化调用的输出,这样如果你从两个不同的线程调用RecurseSome,你将看到第一个线程的整个列表,然后是第二个线程的整个列表线。如果没有锁定,两个线程的输出将交错。
如果不通过拆分方法递归锁定,您可以获得相同的结果:
private static void RecurseSome(int number)
{
lock (obj)
{
RecurseSomeImp(number);
}
}
private static void RecurseSomeImp(int number)
{
Console.WriteLine(number);
if( number < 100 ) // Add a boundary condition
RecurseSomeImp(++number);
}
这实际上会表现得更好,并且获取和释放锁定很快,但不是免费的。
答案 5 :(得分:0)
它与锁无关。检查您的递归代码。在哪里是停止递归的边界情况?
答案 6 :(得分:0)
它是一个连续循环,因为无法确定何时停止递归,并且线程试图访问的对象始终被阻止。