在递归调用中使用lock(obj)

Man*_*ani 21 .net c# multithreading locking

根据我的理解,在运行时完成锁(obj)的代码块之前不会释放锁(因为当块完成时,它会调用Monitor.Exit(obj).

有了这种理解,我无法理解以下代码行为背后的原因:

private static string obj = "";
        private static void RecurseSome(int number)
        {
            Console.WriteLine(number);
            lock (obj)
            {
                RecurseSome(++number);
            }
        }
Run Code Online (Sandbox Code Playgroud)

// 电话: RecurseSome(0)

// 输出: 0 1 2 3...... stack overflow exception

必须有一些我缺少的概念.请帮忙.

小智 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关键字确保一个线程不进入代码的关键部分,而另一个线程处于临界区.如果另一个线程试图输入锁定的代码,它将等待,阻止,直到该对象被释放.

注意线程引用和强调"另一个"线程.


Ste*_*ven 35

不要锁定字符串对象.这可能会导致意外行为,例如应用程序中的死锁.您当前正在锁定空字符串,这更糟糕.整个程序集使用相同的空字符串.并使事情变得更糟; 作为优化,CLR在AppDomains上重用字符串.锁定字符串意味着您可能正在进行跨域锁定.

使用以下代码作为锁定对象:

private readonly static object obj = new object();
Run Code Online (Sandbox Code Playgroud)

UPDATE

事实上,我认为允许锁定任何东西是安全的,这是.NET框架中的一个主要设计缺陷.相反,他们应该创建某种SyncRoot密封类,只允许该lock语句并Monitor.Enter接受实例SyncRoot.这会给我们带来很多苦难.我确实知道这个漏洞来自哪里; Java具有相同的设计.

  • +1是唯一一个选择它的人.字符串可以被实现,这意味着奇怪的行为. (6认同)
  • @TomTom:不要使用Guid或int!仔细考虑将它们传递给Monitor.Enter时会发生什么. (5认同)
  • @Steven:我同意你的看法,锁定任何东西的能力,以及随之而来的内存使用量的增加,使得指向永远不会使用的同步块的指针具有严重的缺点.我不知道我会说这是一个重大缺陷,但我明白你的观点.你对基本原因的理解是正确的; 当您意识到CLR设计人员希望能够支持类似Java的行为的超集时,CLR有几个可疑的设计选择更有意义.这也是我们得到不安全的阵列协方差的地方. (2认同)

Ste*_*ven 9

正如其他人已经注意到的那样,锁是由线程进行的,因此可以正常工作.但是,我想在此添加一些内容.

微软的并发专家Joe Duffy有许多关于并发的设计规则.他的一个设计规则是:

9.避免在设计中锁定递归.尽可能使用非递归锁.

递归通常表示同步设计中的过度简化通常会导致代码不太可靠.一些设计使用锁递归作为一种方法来避免将函数拆分为带锁的函数和假设已经采用锁的函数.这无疑会导致代码大小的减少,从而缩短写入时间,但最终导致设计更加脆弱.

(来源)

要防止递归锁定,请将代码重写为以下内容:

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);
}
Run Code Online (Sandbox Code Playgroud)

此外,我的代码将抛出一个StackOverflowException,因为它永远不会以递归方式调用自身.您可以按如下方式重写您的方法:

private static void RecurseSome(int number)
{
    Console.WriteLine(number);
    if (number < 100)
    {
        RecurseSome(++number);
    }
}
Run Code Online (Sandbox Code Playgroud)


Meh*_*ari 6

锁由当前线程拥有.递归调用也是在当前线程上进行的.如果另一个线程试图获取锁,它将阻止.