我有一个线程,它会一直运行,直到另一个线程更改的整数达到特定值。
int cur = this.m_cur;
while (cur > this.Max)
{
// spin until cur is <= max
cur = this.m_cur;
}
这个程序需要将 this.m_cur 声明为易变量才能正常工作吗?是否可能由于编译器优化而导致程序一直处于循环中?
我有一个线程,它会一直运行,直到另一个线程更改的整数达到特定值。
int cur = this.m_cur;
while (cur > this.Max)
{
// spin until cur is <= max
cur = this.m_cur;
}
这个程序需要将 this.m_cur 声明为易变量才能正常工作吗?是否可能由于编译器优化而导致程序一直处于循环中?
是的,这是一个严格要求。即时编译器允许将m_cur的值存储在处理器寄存器中,而不刷新它从内存中的值。实际上,x86的jit编译器会这样做,而x64的jit则不会(至少我上次查看的时候是这样)。
volatile关键字是必需的以抑制此优化。
在拥有弱内存模型的Itanium核心上,“volatile”意味着完全不同的事情。不幸的是,这就是进入MSDN库和C#语言规范的内容。在ARM核心上,它将意味着什么还有待观察。
BackgroundWorker
代码中删除Component
肯定会破坏它。但是,我仍然可以通过对更简单的类进行子类化来演示问题。JIT编译器必须有一些相当复杂的规则来执行这些优化。这也让我想到,在不同的框架版本中,BackgroundWorker
实际上可能存在一个非常微妙的错误,正在等待显现。微软真的应该在那个底层字段中添加volatile
。 - Brian Gideonclass 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!
}
}
这取决于如何修改m_cur。如果使用普通的赋值语句,例如m_cur--;
,那么它确实需要是易失性的。但是,如果使用其中一个Interlocked操作进行修改,则不需要易失性,因为Interlocked的方法会自动插入内存屏障以确保所有线程都能接收到消息。
一般来说,使用Interlocked修改在线程之间共享的原子值是更可取的选择。它不仅为您处理了内存屏障,而且还比其他同步选项更快。
话虽如此,像其他人所说的轮询循环非常浪费资源。最好暂停需要等待的线程,并让修改m_cur的人在时机成熟时负责唤醒它。根据您的具体需求,Monitor.Wait()和Monitor.Pulse()以及AutoResetEvent可能非常适合这个任务。