不; 单独的Interlocked-at-Write-Only 并不能确保代码中的变量读取实际上是新鲜的;即使在“强内存模型”下,不能正确读取字段的程序也可能不是线程安全的。这适用于任何形式的分配给线程之间共享的字段。
这是一个由于 JIT 而永远不会终止的代码示例。(它是从.NET 中的 Memory Barriers修改为针对该问题更新的可运行 LINQPad 程序)。
// Run this as a LINQPad program in "Release Mode".
// ~ It will never terminate on .NET 4.5.2 / x64. ~
// The program will terminate in "Debug Mode" and may terminate
// in other CLR runtimes and architecture targets.
class X {
// Adding {volatile} would 'fix the problem', as it prevents the JIT
// optimization that results in the non-terminating code.
public int terminate = 0;
public int y;
public void Run() {
var r = new ManualResetEvent(false);
var t = new Thread(() => {
int x = 0;
r.Set();
// Using Volatile.Read or otherwise establishing
// an Acquire Barrier would disable the 'bad' optimization.
while(terminate == 0){x = x * 2;}
y = x;
});
t.Start();
r.WaitOne();
Interlocked.Increment(ref terminate);
t.Join();
Console.WriteLine("Done: " + y);
}
}
void Main()
{
new X().Run();
}
.NET 中内存屏障的解释:
这次是 JIT,而不是硬件。很明显,JIT 已经缓存了变量 terminate [在 EAX 寄存器中的值,并且] 程序现在卡在上面突出显示的循环中..
在 while 循环中使用 alock
或添加 a都可以解决问题。Thread.MemoryBarrier
或者你甚至可以使用Volatile.Read
[or a volatile
field]。这里内存屏障的目的只是为了抑制 JIT 优化。现在我们已经了解了软件和硬件如何重新排序内存操作,是时候讨论内存屏障了。
也就是说,在读取端需要一个额外的屏障构造来防止编译和 JIT 重新排序/优化问题:这是与内存一致性不同的问题!
在此处添加volatile
会阻止JIT 优化,从而“解决问题”,即使这样会导致警告。该程序也可以通过使用Volatile.Read
导致障碍的各种其他操作之一来纠正:这些障碍与底层硬件内存栅栏一样是 CLR/JIT 程序正确性的一部分。