我有一个与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);
}
}
我担心的是,如果保证线程B在没有使用 volatile 的情况下看到变量A值为1?在主线程中,我只在构造函数中为variableA赋值1。之后我没有触及变量A,它仅在线程B中使用,因此可能不需要锁定。
但是,是否保证主线程将刷新其缓存并将variableA内容写入主内存,因此第二个线程可以读取新分配的值?
另外,是否保证第二个线程将从主内存中读取variableA内容?可能会发生一些编译器优化,并且线程B可以从缓存而不是主内存中读取variableA内容吗?当指令的顺序发生变化时,可能会发生这种情况。
当然,将 volatile 添加到variableA声明将使代码正确。但是,它是否必要?我问,因为我在构造函数中编写了一些带有一些非易失性变量初始化的代码,稍后某些Timer线程使用这些变量,我不确定它是否完全正确。
Java中的相同代码怎么样?
谢谢,Michal
答案 0 :(得分:5)
有很多地方会创建隐式内存障碍。这是其中之一。启动线程创建完全障碍。因此,对variableA
的写入将在线程启动之前提交,并且将从主存储器获取第一次读取。当然,在微软的CLR实现中,这有点不成比例,因为写入已经具有易失性语义。但是ECMA规范中没有提出相同的保证,因此理论上可能Mono的实现在这方面可能会有不同的行为。
我担心的是,是否有保证 线程B将看到变量A. 值1不使用volatile?
在这种情况下......是的。但是,如果您继续在第二个帖子中使用variableA
,则在第一次阅读后没有任何保证,它将看到更新。
但是,它是否保证主力 线程将刷新他的缓存并写入 变量A的内容为main 内存,所以第二个线程可以读取 新指定的值?
是
此外,它是否有保证 第二个线程会读取 变量来自main的内容 存储器?
是的,但仅限于第一次阅读。
当然,添加挥发性成分 variableA声明将使 代码正确。但是,这是必要的吗?
在这个非常具体而狭隘的案例中......没有。但是,通常建议您在这些方案中使用volatile
关键字。它不仅会使代码线程安全,因为场景变得更复杂,而且它还有助于记录该字段将被多个线程使用的事实,并且您已经考虑了使用锁定的含义 - 免费策略。
答案 1 :(得分:4)
Java中的相同代码肯定没问题 - 新线程的创建有效地成为一种障碍。 (程序文本中的所有操作都比线程创建“在新线程启动之前发生”。)
然而,我不知道.NET在新线程创建方面有什么保证。更令人担忧的是使用Control.BeginInvoke
之类的时候读取延迟的可能性......我没有看到任何关于这些情况的内存障碍的保证。
说实话,我怀疑没关系。我怀疑需要在这样的线程之间进行协调的任何事情(创建新的或者调用现有的调用)都将在所涉及的两个线程上使用完整的内存屏障。但是,你绝对是正确的,我希望你能从比我聪明的人那里得到一个更确定的答案。你可能想给Joe Duffy发电子邮件,以便对此有所了解......
答案 2 :(得分:4)
但是,是否保证主线程将刷新其缓存并将variableA内容写入主内存,
是的,这是由MS CLR内存模型保证的。对于CLI的其他实现不一定如此(即,我不确定Mono)。 ECMA标准不要求它。
所以第二个线程可以读取新分配的值吗?
这要求缓存已刷新。它可能由Thread的创建保证(如Jon Skeet所说)。然而,前一点无法保证。缓存在每次写入时刷新,但不会在每次读取时刷新。
您可以使用VolatileRead(ref variableA)
来确定,但建议(Jeffrey Richter)使用Interlocked
类。请注意,VolatileWrite()
在MS.NET中是多余的。