10

所以我很高兴地从 Eric Lippert 那里读到这篇文章,当然,还有极好的评论,其中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

4

5 回答 5

6

这不是死锁,因为您没有做任何应该阻止的事情,也没有做任何应该打破的事情。

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

如果您跟踪打印输出显示的路径,则不会阻塞:

  1. 打电话Go(我怀疑)
  2. 输入Bstatic构造函数),因为它没有被初始化。
  3. 打印
  4. 采用A.Initialize()
  5. Astatic构造函数需要先执行
  6. 打印
  7. 采用B.Initialize()
  8. B不需要初始化,但它没有处于完成状态(幸运的是没有设置变量,所以没有任何中断)
  9. 打印出来,然后返回
  10. 打印出(从Astatic构造函数),然后返回
  11. A.Initialize()终于可以调用了,因为A被初始化了
  12. 打印出来,然后返回
  13. 打印出(从Bstatic构造函数),然后返回
  14. Go

您真正做的唯一一件事是呈现不安全状态的可能性:访问构造函数尚未完成执行的类。那是不安全的代码,虽然没有阻塞,但绝对代表了一个损坏的状态。

于 2013-02-01T07:31:39.610 回答
1

重要的一点是只涉及一个线程。引用博客文章:

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

在 Eric 示例中,有两个线程在等待对方。您只有一个线程,因此不会发生等待,因此:没有阻塞和死锁。

于 2013-02-01T07:32:21.860 回答
1

为什么你认为应该有一个僵局。静态构造函数只被调用一次。无论您执行多少次语句B.Initialize();,它只会在第一次引用 B 时调用 B 类的静态构造函数。在此处查看有关静态构造函数的更多信息。

于 2013-02-01T07:40:56.517 回答
0

静态方法只调用一次,一旦加载,就不会再次调用。如果 A.Initialize()方法调用B.Initialize()方法,则将是死锁,反之亦然。

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

于 2013-02-01T07:20:13.487 回答
-3

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

于 2013-02-01T07:28:41.700 回答