在其他线程创建之前初始化C#内存模型和非易失性变量

Mic*_*zyk 6 c# java multithreading synchronization volatile

我有一个与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);
  }
}
Run Code Online (Sandbox Code Playgroud)

我担心的是,如果保证线程B在不使用volatile的情况下看到变量A值为1 ?在主线程中,我只在构造函数中为variableA赋值1.之后我没有触及变量A,它只在线程B中使用,因此可能不需要锁定.

但是,是否保证主线程将刷新其缓存并将variableA内容写入主内存,因此第二个线程可以读取新分配的值?

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

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

Java中的相同代码怎么样?

谢谢,米哈尔

Bri*_*eon 5

有很多地方会创建隐式内存障碍.这是其中之一.启动线程创建完全障碍.因此,写入variableA将在线程启动之前提交,并且将从主存储器获取第一次读取.当然,在微软的CLR实现中,这有点不成比例,因为写入已经具有易失性语义.但是ECMA规范中没有提出相同的保证,因此理论上可能Mono的实现在这方面可能会有不同的表现.

我担心的是,如果保证线程B在不使用volatile的情况下看到变量A值为1?

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

但是,是否保证主线程将刷新其缓存并将variableA内容写入主内存,因此第二个线程可以读取新分配的值?

是.

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

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

当然,将volatile添加到variableA声明将使代码正确.但是,它是否必要?

在这个非常具体和狭窄的情况下......没有.但是,通常建议您volatile在这些方案中使用关键字.它不仅会使代码线程安全,因为场景变得更复杂,但它也有助于记录该字段将由多个线程使用的事实,并且您已考虑使用锁定的含义 - 免费策略.


Jon*_*eet 4

Java 中的相同代码绝对没问题——新线程的创建实际上充当了一种屏障。(程序文本中早于线程创建的所有操作都“发生在”新线程启动之前。)

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

说实话,我怀疑这没什么问题。我怀疑任何需要在像这样的线程之间进行协调的事情(无论是创建一个新线程还是编组对现有线程的调用)都将在所涉及的两个线程上使用完整的内存屏障。然而,你的担心是绝对正确的,我希望你能从比我更聪明的人那里得到更明确的答案。您可能想给 Joe Duffy 发电子邮件以了解他对此的看法......