我有一个线程旋转,直到另一个线程更改的 int 是某个值。
int cur = this.m_cur;
while (cur > this.Max)
{
// spin until cur is <= max
cur = this.m_cur;
}
是否需要将 this.m_cur 声明为 volatile 才能正常工作?由于编译器优化,这是否有可能永远旋转?
我有一个线程旋转,直到另一个线程更改的 int 是某个值。
int cur = this.m_cur;
while (cur > this.Max)
{
// spin until cur is <= max
cur = this.m_cur;
}
是否需要将 this.m_cur 声明为 volatile 才能正常工作?由于编译器优化,这是否有可能永远旋转?
是的,这是一个硬性要求。允许即时编译器将 m_cur 的值存储在处理器寄存器中,而无需从内存中刷新它。x86 抖动实际上有,x64 抖动没有(至少在我最后一次查看时)。
需要volatile关键字来抑制这种优化。
易失性在 Itanium 内核上意味着完全不同的东西,这是一种内存模型较弱的处理器。不幸的是,这就是它成为 MSDN 库和 C# 语言规范的原因。这对 ARM 内核意味着什么还有待观察。
下面的博客有一些关于 c# 内存模型的有趣细节。简而言之,使用 volatile 关键字似乎更安全。
http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/
从下面的博客
class Test
{
private bool _loop = true;
public static void Main()
{
Test test1 = new Test();
// Set _loop to false on another thread
new Thread(() => { test1._loop = false;}).Start();
// Poll the _loop field until it is set to false
while (test1._loop == true) ;
// The loop above will never terminate!
}
}
有两种可能的方法可以终止 while 循环: 使用锁来保护对 _loop 字段的所有访问(读取和写入) 将 _loop 字段标记为易失性 读取非易失性字段可能观察到的原因有两个过时的值:编译器优化和处理器优化。
这取决于如何修改 m_cur。如果它使用的是普通的赋值语句,例如m_cur--;
,那么它确实需要是 volatile 的。但是,如果使用Interlocked操作之一对其进行修改,则不会,因为 Interlocked 的方法会自动插入内存屏障以确保所有线程都获得备忘录。
通常,使用 Interlocked 修改跨线程共享的原子值是更可取的选择。它不仅为您解决了内存障碍,而且还往往比其他同步选项快一点。
也就是说,就像其他人所说的那样,轮询循环非常浪费。最好暂停需要等待的线程,让修改 m_cur 的人负责在时机成熟时将其唤醒。Monitor.Wait() 和 Monitor.Pulse()以及AutoResetEvent 都可能非常适合该任务,具体取决于您的特定需求。