【问题标题】:Can a constructor be pre-empted?构造函数可以被抢占吗?
【发布时间】:2013-09-14 11:34:14
【问题描述】:

在 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. 被构造的对象是不是在构造函数退出后才分配给共享变量,从而完全避开了这个问题?

【问题讨论】:

  • 数字 3 是正确的。一个对象的构造函数代码不能超过一个线程。
  • 这个问题似乎是错误的。如果你有一个 A 的对象,你不能做 objOfA.A().... 构造函数只在使用 new 关键字创建对象时被调用。一旦创建了对象,就无法在创建的对象上显式调用构造函数。
  • 一个对象的构造函数永远不能由多个线程进入(显然所有对象都只构造一次)。
  • @Jon C# 和公开可用的 .NET Framework 类不允许对象多次调用其构造函数(即使它可以从 IL 中调用),但它们确实允许对象不'根本没有调用它们的构造函数。
  • 操作系统可以随时抢占任何用户模式代码。它不知道构造函数是什么,也不知道 .NET 是什么。

标签: c# multithreading


【解决方案1】:

我认为接受的答案不正确。 Igor Ostrovsky 的“The C# Memory Model in Theory and Practice”(part 2 here) 彻底解释了这些问题,其中一个示例准确地说明了您的问题。

它给出了代码

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 可以解决问题。

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

【讨论】:

  • +1:你说得对。接受的答案在技术上并不正确。一个对象实例可以在构造函数执行之前被赋值给变量!我也在我的回答here 中解释了这是如何发生的。
  • 我对 C# 不太了解,但是如何在右手函数(构造函数)执行完成之前分配左手值?并且使用 volatile 与您正在讨论的内容无关。 java中的至少易失性提供可见性而不是原子性。你清楚吗?
  • @NarendraPathai 在单个线程中,这是不可能的,你永远不会看到。但是,如果没有volatile 或其他同步,运行时可能会优化而不考虑其他线程。 _box = new BoxedInt2(); 可以变成(伪代码)var tmp = newuninit BoxedInt2; tmp._value = 42; _box = tmp;,之后,如果tmp._value = 42; _box = tmp; 重新排序,单个线程看不到任何差异,因此允许重新排序它们。如果 (fe) 这允许对 _box 的多个不同分配组合在一起以仅存储最后一个值,则重新排序可能是有意义的。
  • @JonSkeet 我希望乔恩可以对此给出解释。我仍然有疑问。
【解决方案2】:

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

是的。只有在构造函数返回时构造对象时,才会将引用的值分配给变量。所以只有在那之后,其他线程才能调用来检查值是true还是false

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

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

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

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

    //initializing instance variables
}

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

Don't publish the "this" reference during construction, Java。不确定是否同样适用于 C#

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

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

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

如果是这样,这是否意味着应该有同步代码这样的 作为构造函数中的锁?

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

【讨论】:

  • 两个线程永远都不能进入同一个对象的构造函数。而在不同的对象上你根本不在乎(除非static 成员发挥作用)。
  • @Jon Yupp 添加了那个打孔字符ever。谢谢。
  • 如果假设我确实将“this”添加到共享列表中会发生什么? A)在C#中可能吗? B) 那么两个线程是否有可能在构造函数仍在运行时访问该对象?
  • @ose 是的,在这种情况下,当您将当前对象发布到共享列表时,其他线程可以看到未完全创建的对象。
  • @ose 是的,您可以使用 this 并将其添加到 c# AFAIK 中的列表中。
猜你喜欢
  • 2021-05-11
  • 1970-01-01
  • 1970-01-01
  • 2011-04-30
  • 1970-01-01
  • 1970-01-01
  • 2011-12-30
相关资源
最近更新 更多