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程序的正确性一样,都是底层硬件内存的一部分。
上述就是C#学习教程:Interlocked是否提供所有线程的可见性?分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/949679.html