代码之家  ›  专栏  ›  技术社区  ›  Manish Basantani

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

  •  20
  • Manish Basantani  · 技术社区  · 14 年前

    根据我的理解,在运行时完成锁(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

    一定有什么概念我不知道。请帮忙。

    7 回复  |  直到 11 年前
        1
  •  35
  •   culix    11 年前

    锁知道是哪个线程锁的。如果同一个线程再次出现,它只会增加一个计数器,而不会阻塞。

    所以,在递归中,第二个调用也会进入——锁在内部增加锁计数器——因为它是同一个线程(它已经持有锁)。

    MS-HELP://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关键字确保一个线程不会进入代码的关键部分 而另一根线 在关键部分。如果另一个线程试图输入一个锁定的代码,它将等待、阻止,直到对象被释放。

    注意线程引用和对“另一个”线程的强调。

        2
  •  34
  •   Steven    11 年前

    请做 不是 锁定字符串对象。这可能会导致意外行为,如应用程序中的死锁。您当前正在锁定空字符串,这更糟。整个程序集使用相同的空字符串。更糟糕的是,作为优化,clr在appdomains上重用字符串。锁定字符串意味着您可能正在进行跨域锁定。

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

    private readonly static object obj = new object();
    

    更新

    事实上,我认为可以肯定地说,允许锁定任何东西是.NET框架中的一个主要设计缺陷。相反,他们应该创造某种 SyncRoot 密封类,只允许 lock 声明和 Monitor.Enter 接受 合成植物 . 这会让我们省下很多痛苦。我确实理解这个缺陷来自何处;Java具有相同的设计。

        3
  •  9
  •   Steven    14 年前

    正如其他人已经注意到的,锁是由线程固定的,因此可以工作。不过,我想补充一点。

    Joe Duffy 一位来自微软的并发专家,有很多关于并发的设计规则。他的设计规则之一是:

    9。在设计中避免锁递归。使用非递归锁 如果可能的话。

    递归通常表示 同步设计通常 导致代码不太可靠。一些 设计使用锁递归作为 避免将函数拆分为 带锁和假想的 锁已经锁上了。这个罐头 无可否认地导致了代码的减少 尺寸,因此更短 是时候写了,但结果是 最后是易碎的设计。

    ( 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);
        }
    }
    
        4
  •  6
  •   Mehrdad Afshari    14 年前

    锁属于当前线程。递归调用也在当前线程上进行。如果 另一根线 试图获取锁,它会阻塞。

        5
  •  1
  •   Paul Alexander    14 年前

    如果你问堆栈溢出异常-那是因为里面没有什么东西可以从递归中分离出来。堆栈空间通常只有几个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);
    }
    

    这实际上会有更好的表现,因为获取和释放锁很快,但不是免费的。

        6
  •  0
  •   Fakrudeen    14 年前

    这和锁无关。检查递归代码。停止递归的边界条件在哪里?

        7
  •  0
  •   hallie    14 年前

    这是一个连续循环,因为无法确定何时停止递归,并且线程试图访问的对象始终被阻止。