Interlocked是否提供所有线程的可见性?

假设我有一个变量“counter”,并且有几个线程通过使用Interlocked访问和设置“counter”的值,即:

int value = Interlocked.Increment(ref counter); 

 int value = Interlocked.Decrement(ref counter); 

我可以假设,Interlocked所做的更改将在所有线程中可见吗?

如果没有,我该怎么做才能使所有线程同步变量?

编辑:有人建议我使用volatile。 但是当我将“计数器”设置为volatile时,会有编译器警告“对volatile字段的引用不会被视为volatile”。

当我阅读在线帮助时,它说:“通常不应使用ref或out参数传递易失性字段”。

x86 CPU上的InterlockedIncrement / Decrement(x86的lock add / dec)会自动创建内存屏障 ,从而提供对所有线程的可见性(即,所有线程都可以按顺序查看其更新,如顺序内存一致性)。 内存屏障使所有待处理的内存加载/存储都完成。 尽管C#和Java(以及一些C / C ++编译器)强制执行volatile以使内存屏障,但volatile与此问题无关。 但是,联锁操作已经由CPU具有内存屏障。

另请查看stackoverflow中的另一个答案 。

请注意,我假设C#的InterlockedIncrement / Decrement是x86的lock add / dec的内在映射。

我可以假设,Interlocked所做的更改将在所有线程中可见吗?

这取决于您如何阅读该值。 如果您“只是”读取它,那么不会,除非您将其标记为易失性,否则这在其他线程中并不总是可见。 但这会引起恼人的警告。

作为替代(并且非常优选的IMO),使用另一个Interlocked指令读取它。 这将始终在所有线程上看到更新的值:

 int readvalue = Interlocked.CompareExchange(ref counter, 0, 0); 

返回读取的值,如果为0,则将其与0交换。

动机:警告暗示事情不对; 结合这两种技术(易失性和互锁)并不是预期的方法。

更新:似乎另一种可靠的32位读取而不使用“volatile”的方法是使用本答案中建议的Thread.VolatileRead 。 还有一些证据表明我使用Interlocked进行32位读取是完全错误的,例如这个Connect问题 ,尽管我想知道这种区别是否有点迂腐。

我的真正含义是:不要将这个答案作为你唯一的来源; 我对此表示怀疑。

实际上,他们不是。 如果你想安全地修改counter ,那么你正在做正确的事情。 但是如果你想直接读取counter ,你需要将它声明为volatile 。 否则,编译器没有理由相信counter会改变,因为Interlocked操作是在它可能看不到的代码中。

Interlocked确保一次只有一个线程可以更新该值。 要确保其他线程可以读取正确的值(而不是缓存值),请将其标记为volatile。

public volatile int Counter;

没有; 只有Interlocked-at-Write-Only才能确保代码中的变量读取实际上是新鲜的; 即使在“强内存模型”下,也不能正确读取字段的程序可能不是线程安全的 。 这适用于分配给线程之间共享的字段的任何forms。

这是一个永远不会因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寄存器中,并且]程序现在卡在上面突出显示的循环中。

使用lock或在while循环中添加Thread.MemoryBarrier将解决问题。 或者您甚至可以使用Volatile.Read [或volatile字段]。 这里内存屏障的目的只是为了抑制JIT优化。 现在我们已经看到软件和硬件如何重新排序内存操作 ,现在是讨论内存障碍的时候了。

也就是说,读取端需要一个额外的屏障结构,以防止编译和JIT重新排序/优化问题这是一个与内存一致性不同的问题!

在这里添加volatile阻止 JIT优化,从而“解决问题”,即使这样会导致警告。 此程序也可以通过使用Volatile.Read或导致障碍的各种其他操作之一来纠正:这些障碍与CLR / JIT程序的正确性一样,都是底层硬件内存的一部分。