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

本文关键字:死锁 这种情况下 为什么 | 更新日期: 2023-09-27 17:56:51

所以我很高兴地读到埃里克·利珀特(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的静态构造函数完成之前调用的,我认为这是不允许的。

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

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

您没有使用BA的任何资源,反之亦然。因此,您的循环依赖是"安全的",因为没有什么会爆炸。

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

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

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

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

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

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

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

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

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

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