为什么在这种情况下没有死锁
本文关键字:死锁 这种情况下 为什么 | 更新日期: 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
的静态构造函数完成之前调用的,我认为这是不允许的。
这不是一个死锁,因为你没有做任何应该阻止的事情,也没有做任何应该破坏的事情。
您没有使用B
内A
的任何资源,反之亦然。因此,您的循环依赖是"安全的",因为没有什么会爆炸。
如果您跟踪打印输出显示的路径,则不应阻止任何内容:
- 打电话
Go
(我怀疑) - 输入
B
(static
构造函数),因为它未初始化。 - 打印输出
- 使用
A.Initialize()
-
A
的static
构造函数需要首先执行 - 打印输出
- 使用
B.Initialize()
-
B
不需要初始化,但它没有处于完成状态(幸运的是没有设置变量,所以没有任何中断) - 打印出来,然后返回
- 打印输出(从
A
的构造函数static
),然后返回 -
A.Initialize()
最终可以调用,因为初始化A
- 打印出来,然后返回
- 打印输出(从
B
的构造函数static
),然后返回 -
Go
您真正做的唯一一件事是呈现不安全状态的可能性:访问其构造函数尚未完成执行的类。 这是不安全的代码,虽然没有阻止,但绝对代表一种损坏的状态。
重要的一点是只涉及一个线程。引用博客文章:
然后,静态构造函数启动一个新线程。当该线程启动时,CLR 看到即将在其静态构造函数"正在传输"另一个线程的类型上调用静态方法。它会立即阻止新线程,以便在主线程完成运行类构造函数之前不会启动 Initialize 方法。
在 Erics 示例中,有两个线程相互等待。您只有一个线程,因此不会发生等待,因此:没有阻塞和死锁。
为什么你认为应该有一个僵局。静态构造函数只调用一次。无论您执行语句B.Initialize();
多少次,它只会在第一次引用 B 时调用类 B 的静态构造函数。在此处查看有关静态构造函数的更多信息。
静态方法只调用一次,一旦加载,就不会再次调用。如果A.Initialize()
方法调用B.Initialize()
方法,反之亦然,这将是一个死锁。
因此,任何类首先加载到内存中,静态块都会被执行,然后任何后续调用它 - 因为该类已经加载,因此静态块不会被执行。
类实例实际上是在构造函数调用之前创建的,因此不再需要执行构造函数。