替换成员变量线程的值是否安全?

时间:2012-03-23 13:53:36

标签: c# multithreading thread-safety

在我的应用程序中(用C#编写)我有一个类的实例,其中一个成员变量指向另一个类的实例。第二个实例是只读的,因此该实例的状态在创建后永远不会更改。但在某些情况下,我想用一个新的更新实例替换此实例。在多线程环境中,在第一个实例中替换此引用是否安全?或者这会导致问题吗?

6 个答案:

答案 0 :(得分:9)

简单的写操作本身就是线程安全的。

所以这是好的:

x.y = new Y();   // OK

但以下不是:

if (x.y == null)
    x.y = new Y();   // might overwrite a non-null x.y

所以这取决于你想要如何使用它,以及线程的期望。但原子性意味着你在x.y

中永远不会有无效(半写)引用

答案 1 :(得分:6)

是的,在参考读/写是原子的意义上它是“安全的”。您的客户端代码将始终获得有效的实例。

显然,您无法保证客户端代码会立即获得新实例。

答案 2 :(得分:3)

引用替换是32位环境下的32位整数写操作,64位环境下的64位整数写操作。写操作确实分两步执行:

  • 阅读新值
  • 写一个新值

我认为默认情况下它不是线程安全的

答案 3 :(得分:3)

  

在第一个实例中替换此引用是安全的   多线程环境?

很抱歉在这里wishy-washy,但这取决于你如何定义“安全”。

  • 它不会腐败状态是安全的。这是因为参考分配是原子的。
  • 安全,因为它可能会导致意外行为。

意外行为可分为三大类。

  • 读取后写入的组合可能会导致竞争条件。
  • 由于编译器和硬件优化,一个线程可能无法观察到另一个线程所做的更改。
  • 一个线程可能会观察到两个不同的参考变量读取之间的变化。

案例#1:因此,在第一种情况下,两个线程可以竞争以虚假借口进行分配。最好的例子是创建一个单身人士。

public static Singleton GetSingleton()
{
  // More than one thread could read null and attempt instantiation here.
  if (instance == null)
  {
    instance = new Singleton();
  }
  return instance;
} 

案例#2:在第二种情况下,即使另一个线程发布了新实例,一个线程也可能继续读取旧实例。考虑这个例子。

public class Example
{
  private SuperHero hero = new Superman();

  void ThreadA()
  {
    while (true)
    {
      Thread.Sleep(1000);
      hero = new CaptainAmerica();
      Thread.Sleep(1000);
      hero = new GreenLantern();
      Thread.Sleep(1000);
      hero = new IronMan();
    }

  void ThreadB()
  {
    while (true)
    {
      hero.FightTheBadGuys();
    }
  }
}

问题在于,编译器或硬件可能会通过“解除”循环外部ThreadB的读取来优化hero

void ThreadB()
{
  SuperHero local = hero;
  while (true)
  {
    local.FightTheBadGuys();
  }
}

不难看出我们的一个超级英雄会变得非常疲惫。然后,他又是一个超级英雄,所以事后可能没有问题。我想在这里提出的观点是,这个 staleness 工件可能会或可能不会成为问题,具体取决于您的情况。

案例#3:在第三种情况下,线程可能错误地认为变量将在两次读取之间引用相同的实例。

void SomeThread()
{
  var result1 = instance.DoSomething();
  // The variable instance could get changed here!
  var result2 = instance.DoSomethingElse();
  // Are result1 and result2 in a mutually consistent state here?
}

在上面的示例中,result1result2可能不处于相互一致的状态。这是因为它们来自在两个不同实例上运行的操作。同样,这可能是也可能不是问题,具体取决于您的具体情况。

答案 4 :(得分:1)

不,由于缓存(某些架构需要像这样的内存屏障),编译器优化和并发访问,它不是线程安全的。
您有以下选择:

  • 将该字段声明为volatile
  • 使用Interlocked.Exchange()替换值。

阅读MSDN文档以选择更适合您的方法(快速:volatile 应该更快,但您无法保存旧值并在一次原子操作中编写新值。)

<强> EDITED
我想我必须明确我的意思(许多人似乎认为 atomic 线程安全的同义词):

  

在多线程环境中,在第一个实例中替换此引用是否安全?

不,严格来说,这不是线程安全的。你不知道会发生什么,所以它不安全。

  

或者这会导致问题吗?

这取决于具体情况。如果一个线程不使用正确的套接字连接(或一个封闭的连接),那么这将是一个大问题。如果一个线程不使用正确的颜色来格式化其输出,那么可能不会成为问题。

答案 5 :(得分:0)

使用以下代码,您希望使用新实例更新对象。它是线程安全的代码。

MyClass obj = null;
Interlocked.CompareExchange(ref obj, new MyClass(), null);