10

构造函数是否可以在 C# 中被抢占?

例如,考虑以下代码:

public class A
{
    public bool ready = true;

    public A()
    {
        ready = false; // Point #1
        // Other initialization stuff
        ready = true; // Point #2
    }
}

在代码的其他地方,两个线程可以访问 A 类型的变量,第一个线程调用构造函数,该构造函数在点 #1 被抢占。然后第二个线程测试ready并发现它仍然是真的,因此它做了一些坏事。

这种情况可能吗?

进一步来说:

  1. 构造函数可以被抢占吗?
  2. 如果是这样,这是否意味着lock构造函数中应该有同步代码?
  3. 构造函数退出后,正在构造的对象是否只分配给共享变量,从而完全避免了这个问题?
4

2 回答 2

5

我不认为接受的答案是正确的。Igor Ostrovsky 的“理论与实践中的 C# 内存模型”(此处为第 2 部分)彻底解释了这些问题,其中一个示例准确地说明了您所问的问题。

它给出了代码

class BoxedInt2
{
  public readonly int _value = 42;
  void PrintValue()
  {
    Console.WriteLine(_value);
  }
}

class Tester
{
  BoxedInt2 _box = null;
  public void Set() {
    _box = new BoxedInt2();
  }
  public void Print() {
    var b = _box;
    if (b != null) b.PrintValue();
  }
}

和注意事项:

由于 BoxedInt 实例被错误地发布(通过非易失性字段 _box),调用 Print 的线程可能会观察到部分构造的对象!同样,使 _box 字段 volatile 可以解决问题。

我强烈建议阅读整篇文章,这是一本非常有用的文章。

于 2013-09-14T16:30:10.740 回答
4

构造函数退出后,正在构造的对象是否只分配给共享变量,从而完全避免了这个问题?

的。只有在构造函数返回时构造对象时,才会将引用的值分配给变量。因此,只有在那之后,其他线程才能调用以检查 value 是否为trueorfalse

两个线程不能同时进入构造函数。

安全发布问题 - 发布未完全创建的对象

但是,如果您要发布对某个共享列表的当前引用,则很容易出错。注意不要在你的构造函数中做这样的事情

A(List sharedList){
    sharedList.add(this);

    //initializing instance variables
}

请原谅 Java 代码。因此,在这种情况下,您将不完整的创建对象发布到共享列表,其他线程可以访问该列表并可能导致许多问题。

在构建过程中不要发布“this”引用,Java。不确定是否同样适用于 C#

那么这让我回到了最初的问题,在 C# 中构造函数可以被抢占吗?

就抢占而言,的,IMO可以在构造函数运行时中断线程。构造函数就像一个普通的方法,但具有特殊的语义。

根本不会影响代码的线程安全,因为只有一个线程可以在对象的构造函数中。所以它是完全线程安全的,直到你不逃避不完全构建this的引用。

如果是这样,是不是意味着构造函数中应该有锁等同步代码?

构造函数中不能只有一个线程,因此它本质上是线程安全的

于 2013-09-14T11:39:46.440 回答