C# 中的静态构造函数死锁是否与 ECMA CLI 标准相矛盾?
Do static constructor deadlocks in C# contradict the ECMA CLI standard?
这是标准中我感到困惑的部分:http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#page=178&zoom=auto,87,610%22
2.1. If the type is not yet initialized, try to take an initialization lock.
2.2.1. If not successful, see whether this thread or any thread waiting for this thread to complete already holds the lock.
2.2.2. If so, return since blocking would create a deadlock. This thread will now see an incompletely initialized state for the type, but no deadlock will arise.
下面的代码在我测试的时候死锁了,好像是违背标准的:
public static class Foo {
static Foo() {
var otherThread = new Thread(() => { Thread.Sleep(1000); SomeFunction(); });
otherThread.Start();
otherThread.Join();
}
public static void SomeFunction() {
}
}
class Program {
static void Main() {
Foo.SomeFunction();
}
}
根据标准,我预计会发生以下情况:
- 主线程获取 Foo 上的初始化锁。
- 主线程运行Foo的静态构造函数
- 主线程创建otherThread并启动它。
- otherThread 开始等待一秒钟,从而确保第 5 点发生在第 6 点之前。
- 主线程开始等待其他线程完成。
- otherThread 尝试获取 Foo 上的初始化锁,但由于主线程持有锁而失败。
- otherThread 放弃执行静态构造函数,因为主线程持有初始化锁并等待 otherThread。
- otherThread 运行 SomeFunction 并成功完成。
- 主线程returns.
这是怎么回事?
"any thread waiting for this thread to complete" 是指任何等待使用静态线程 初始化锁的线程,而不是使用任何可能的同步机制等待的线程。静态初始化机制无法知道某个其他线程正在等待在另一个线程上使用一些完全不同的机制。
引用部分指的是以下示例不会死锁:
public class A
{
static A()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
B.DoNothing();
}
public static void DoNothing() { }
}
public class B
{
static B()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
A.DoNothing();
}
public static void DoNothing() { }
}
private static void Main()
{
Task.Run(() => B.DoNothing());
A.DoNothing();
}
这个例子没有死锁,因为一个线程正在等待另一个线程释放静态初始化器锁,所以当该线程结束请求原始线程拥有的静态初始化器锁时,引用的子句开始并它只是跳过了锁。
这是标准中我感到困惑的部分:http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#page=178&zoom=auto,87,610%22
2.1. If the type is not yet initialized, try to take an initialization lock.
2.2.1. If not successful, see whether this thread or any thread waiting for this thread to complete already holds the lock.
2.2.2. If so, return since blocking would create a deadlock. This thread will now see an incompletely initialized state for the type, but no deadlock will arise.
下面的代码在我测试的时候死锁了,好像是违背标准的:
public static class Foo {
static Foo() {
var otherThread = new Thread(() => { Thread.Sleep(1000); SomeFunction(); });
otherThread.Start();
otherThread.Join();
}
public static void SomeFunction() {
}
}
class Program {
static void Main() {
Foo.SomeFunction();
}
}
根据标准,我预计会发生以下情况:
- 主线程获取 Foo 上的初始化锁。
- 主线程运行Foo的静态构造函数
- 主线程创建otherThread并启动它。
- otherThread 开始等待一秒钟,从而确保第 5 点发生在第 6 点之前。
- 主线程开始等待其他线程完成。
- otherThread 尝试获取 Foo 上的初始化锁,但由于主线程持有锁而失败。
- otherThread 放弃执行静态构造函数,因为主线程持有初始化锁并等待 otherThread。
- otherThread 运行 SomeFunction 并成功完成。
- 主线程returns.
这是怎么回事?
"any thread waiting for this thread to complete" 是指任何等待使用静态线程 初始化锁的线程,而不是使用任何可能的同步机制等待的线程。静态初始化机制无法知道某个其他线程正在等待在另一个线程上使用一些完全不同的机制。
引用部分指的是以下示例不会死锁:
public class A
{
static A()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
B.DoNothing();
}
public static void DoNothing() { }
}
public class B
{
static B()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
A.DoNothing();
}
public static void DoNothing() { }
}
private static void Main()
{
Task.Run(() => B.DoNothing());
A.DoNothing();
}
这个例子没有死锁,因为一个线程正在等待另一个线程释放静态初始化器锁,所以当该线程结束请求原始线程拥有的静态初始化器锁时,引用的子句开始并它只是跳过了锁。