【问题标题】:C# memory model and non volatile variable initialized before the other thread creationC# 内存模型和非易失性变量在其他线程创建之前初始化
【发布时间】:2011-04-05 11:18:55
【问题描述】:

我有一个关于 C# 内存模型和线程的问题。如果没有 volatile 关键字,我不确定以下代码是否正确。

public class A {
  private int variableA = 0;

  public A() {

    variableA = 1;

    Thread B = new Thread(new ThreadStart(() => printA())).Start();
  }

  private void printA() {
    System.Console.WriteLine(variableA);
  }
}

我担心的是,如果不使用 volatile,线程 B 是否会看到值为 1 的变量 A?在主线程中,我只在构造函数中将 1 分配给 variableA。之后我不再接触变量A,它只在线程B中使用,因此可能不需要锁定。

但是,是否保证主线程会刷新他的缓存并将变量A的内容写入主内存,这样第二个线程才能读取新分配的值?

另外,是否保证第二个线程会从主内存中读取变量A的内容?可能会发生一些编译器优化,线程 B 可以从缓存而不是主内存中读取变量 A 的内容吗?当指令的顺序改变时可能会发生这种情况。

当然,将 volatile 添加到 variableA 声明将使代码正确。但是,有必要吗?我问是因为我在构造函数中编写了一些带有一些非易失性变量初始化的代码,这些变量稍后会被一些 Timer 线程使用,我不确定它是否完全正确。

Java 中的相同代码呢?

谢谢,米哈尔

【问题讨论】:

    标签: c# java multithreading synchronization volatile


    【解决方案1】:

    有很多地方会创建隐式内存屏障。这是其中之一。启动线程会创建完整的屏障。因此,对variableA 的写入将在线程启动之前提交,并且第一次读取将从主内存中获取。当然,在 Microsoft 的 CLR 实现中,这有点争议,因为写入已经具有易失语义。但是 ECMA 规范中没有做出相同的保证,因此从理论上讲,Mono 实现可能在这方面表现不同。

    我担心的是是否可以保证 线程 B 将看到 variableA 值 1 而不使用 volatile?

    在这种情况下……是的。但是,如果您在第二个线程中继续使用variableA,则在第一次读取后无法保证它会看到更新。

    但是,是否保证主要 线程将刷新他的缓存并写入 变量A的内容到主 内存,所以第二个线程可以读取 新分配的值?

    是的。

    另外,是否保证 第二个线程将读取 主变量A的内容 内存?

    是的,但仅限于第一次阅读。

    当然,将 volatile 添加到 variableA 声明将使 代码正确。但是,有必要吗?

    在这个非常具体和狭窄的情况下......不。但是,通常建议您在这些情况下使用 volatile 关键字。随着场景变得越来越复杂,它不仅会使您的代码线程安全,而且还有助于记录该字段将被多个线程使用的事实,并且您已经考虑了使用锁的含义-免费策略。

    【讨论】:

    • 嗨,Brian,“是的,但仅限于第一次阅读。” - 你能以某种方式证明这一点吗?有链接吗?
    【解决方案2】:

    Java 中的相同代码绝对没问题 - 新线程的创建实际上起到了一种屏障的作用。 (程序文本中比线程创建更早的所有操作都“发生在”新线程开始之前。)

    但是,我不知道 .NET 在创建新线程方面有什么保证。更令人担忧的是,在使用 Control.BeginInvoke 等时可能会出现延迟读取……我还没有看到针对这些情况的内存屏障的任何保证。

    说实话,我怀疑没关系。我怀疑任何需要在这样的线程之间进行协调的东西(创建新线程或将调用编组到现有线程)都会在所涉及的两个线程上使用完整的内存屏障。但是,您的担心是绝对正确的,我希望您能从比我更聪明的人那里得到更明确的答案。您可能想给 Joe Duffy 发电子邮件,了解他对此的看法……

    【讨论】:

    • 是的,我怀疑是一样的,但想确定一下。我会发邮件给他,谢谢。
    【解决方案3】:

    但是,是否保证主线程会刷新他的缓存并将变量A的内容写入主存,

    是的,这是由 MS CLR 内存模型保证的。对于 CLI 的其他实现不一定如此(即,我不确定 Mono)。 ECMA 标准不需要它。

    所以第二个线程可以读取新分配的值?

    这需要刷新缓存。这可能是通过创建线程来保证的(就像 Jon Skeet 所说的那样)。但是,前一点不能保证。缓存会在每次写入时刷新,但不会在每次读取时刷新。

    您可以通过使用 VolatileRead(ref variableA) 来确保非常确定,但建议 (Jeffrey Richter) 使用 Interlocked 类。请注意,VolatileWrite() 在 MS.NET 中是多余的。

    【讨论】:

      猜你喜欢
      • 2015-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多