C# 不是线程安全的初始化

C# not thread-safe initialization

有两个线程A和B,线程A执行如下赋值:

v = new Vec2()

线程B使用v,例如:

double length = Math.Sqrt(v.x*v.x + v.y*v.y);
Console.WriteLine("length is {0}", length);

v 是一个 non-volatile 字段,Vec2 是一个带有一些初始化的引用类型:

class Vec2
{
   public double x;
   public double y;
   public Vec2()
   {
       // this one is important
       x = 56.0;
       y = 78.0
   }
}

问题是,根据 C# 语言规范(or/and .NET 运行时规范),可能 是不是线程 B 会观察到 v 未初始化?也就是说,线程 B 可能会在例如之前观察到对 v 的引用赋值。 x = 56.0?

编辑:如果是这样,添加volatile是否会确保线程B仅在完全初始化后才观察到v

编辑:如果xy使用字段初始化器初始化,答案会改变吗,即:

class Vec2
{
    public double x = 56.0;
    public double y = 78.0;
}

编辑:我在这里不寻求解决任何问题,只是想知道实际会发生什么。感谢您的回答!

The question is, according to C# language spec (or/and .NET runtime spec), might it be that the thread B will observe v uninitialized?

线程 B 将无法在构造函数 运行 之前访问 v 内的字段,但它可能 "see" vnull,取决于线程 A 中的初始化发生的时间以及初始化和线程 B 访问之间是否存在内存屏障。

一般来说,如果你有两个线程,并且你需要按顺序发生某些事情(在这种情况下,v 在线程 B 中使用之前由线程 A 初始化),你将需要显式添加某种形式的同步。

对于您的情况,像等待句柄这样简单的东西就足够了:

// Assuming the existence of this in scope of both threads:
// ManualResetEvent mre = new ManualResetEvent(false)

在线程 A 中:

v = new Vec2();
mre.Set(); // Denote that initialization is complete

在线程 B 中:

mre.WaitOne(); // Wait (blocking) until initialization is complete
double length = Math.Sqrt(v.x*v.x + v.y*v.y);
// .. Other code as needed

EDIT: if so, would adding volatile ensure that thread B observes v only after it is fully initialized?

Volatile 不是正确的解决方案。如果您需要等待初始化发生,您应该使用同步机制,例如上面详述的那个。

EDIT: will the answer change if x,y are initialized using field initializers, that is:

没有。构造函数和字段初始值设定项都将在 v 成为 "set" 之前全部 运行,但你不能保证 v 不会是 null 没有其他一些同步到位。