为什么在这种情况下没有死锁?

时间:2013-02-01 07:10:54

标签: c# .net

所以我很高兴地从Eric Lippert那里阅读this然后,当然,优秀的评论和其中John Payson说:

  
    

一个更有趣的例子可能是使用两个静态类,因为这样的程序可能会死锁而没有任何可见的阻塞语句。

  

我想,是的,那很容易,所以我把它搞砸了:

public static class A
{     
    static A()
    {
        Console.WriteLine("A.ctor");
        B.Initialize();
        Console.WriteLine("A.ctor.end");
    }

    public static void Initialize()
    {
        Console.WriteLine("A.Initialize");
    }
}
public static class B
{
    static B()
    {
        Console.WriteLine("B.ctor");
        A.Initialize();
        Console.WriteLine("B.ctor.end");
    }

    public static void Initialize()
    {
        Console.WriteLine("B.Initialize");
    }

    public static void Go()
    {
        Console.WriteLine("Go");
    }
}

其输出(在调用B.Go()之后)是:

B.ctor
A.ctor
B.Initialize
A.ctor.end
A.Initialize
B.ctor.end
Go

没有僵局,我显然是一个失败者 - 所以为了使这种尴尬永久化,这是我的问题:为什么这里没有僵局?

B.Initialize的静态构造函数完成之前,B在之前被{{1}}称为,我认为这是不允许的。

5 个答案:

答案 0 :(得分:6)

这不是一个僵局,因为你没有做任何应该阻止的事情,也没有做任何应该破坏的事情。

您未在A内使用B中的任何资源,反之亦然。因此,您的循环依赖是“安全的”,因为没有任何东西会爆炸。

如果您追踪打印输出显示的路径,则不应阻止任何内容:

  1. 致电Go(我怀疑)
  2. 输入Bstatic构造函数),因为它未初始化。
  3. 打印
  4. 使用A.Initialize()
  5. A的{​​{1}}构造函数需要先执行
  6. 打印
  7. 使用static
  8. B.Initialize()不需要初始化,但它没有处于完成状态(幸运的是没有设置变量,因此没有任何中断)
  9. 打印出来,然后返回
  10. 打印出来(来自B的{​​{1}}构造函数),然后返回
  11. 最终可以调用
  12. A,因为static已初始化
  13. 打印出来,然后返回
  14. 打印出来(来自A.Initialize()的{​​{1}}构造函数),然后返回
  15. A
  16. 你真正做的唯一事情就是存在一个不安全状态的可能性:访问一个构造函数没有完成执行的类。这是不安全的代码,虽然没有阻塞,但绝对代表一种破碎的状态。

答案 1 :(得分:1)

重要的是,只涉及一个线程。引自博文:

  

静态构造函数然后启动一个新线程。当该线程启动时,CLR会看到静态方法即将在其静态构造函数“在飞行中”另一个线程的类型上调用。它立即阻止新线程,以便在主线程完成运行类构造函数之前,Initialize方法不会启动。

在Erics示例中,有两个线程互相等待。你只有一个线程,所以没有等待的结果,因此:没有阻塞,没有死锁。

答案 2 :(得分:1)

为什么你认为应该存在僵局。静态构造函数只调用一次。无论执行语句B.Initialize();多少次,它都会在第一次调用B时调用类B的静态构造函数。请参阅有关静态构造函数here的更多信息。

答案 3 :(得分:0)

静态方法只调用一次,一旦加载,则不再调用它们。 如果A.Initialize()方法称为B.Initialize()方法,那将是一个僵局,反之亦然。

所以任何类首次加载到内存中的静态块都会被执行,之后任何后续调用它 - 因为该类已经加载所以静态块不会被执行。

答案 4 :(得分:-3)

类实例实际上是在构造函数调用之前创建的,因此不再需要执行构造函数。