2012-01-04 68 views
19

假设我有一个控制一些循环的执行现场:这是Thread.MemoryBarrier()的正确使用吗?

private static bool shouldRun = true; 

而且我有一个线程在运行,有如下代码:

while(shouldRun) 
{ 
    // Do some work .... 
    Thread.MemoryBarrier(); 
} 

现在,另一个线程可以设置shouldRunfalse,而不使用任何同步机制。

据我了解Thread.MemoryBarrier(),在while循环中进行此调用将阻止我的工作线程获取shouldRun的缓存版本,并有效防止发生无限循环。

是我关于Thread.MemoryBarrier正确的认识?鉴于我有可以设置shouldRun变量(这个不能轻易改变),这是一种合理的方法,以确保我的循环会被任何线程停止一旦shouldRun设置为false线程?

+0

不声明布尔为挥发性做一样的东西?我不确定Thread.MemoryBarrier因此评论没有回答。例如http://www.albahari.com/threading/part4.aspxAnother>“解决此问题的更高级方法是将volatile关键字应用于_complete字段。volatile关键字指示编译器在每个字段上生成获取栅栏从该字段读取数据,并在每次写入该字段时释放一个篱笆“。问候, – 2012-01-04 15:38:02

+3

挥发性是一个非常邪恶的事情,不应该使用:http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different-part -three.aspx – Steven 2012-01-04 15:41:45

+1

大声笑,我没有意识到这一点!作为一名前C/asm程序员,我认为这是一件好事:)感谢您的链接,我会看看。 – 2012-01-04 15:46:18

回答

26

是阅读这正确使用Thread.MemoryBarrier()?

号假设一个线程设置之前的循环,甚至开始执行标志。循环仍然可以执行一次,使用缓存的标志值。 是否正确?这对我来说肯定是不正确的。我期望如果我在循环的第一次执行之前设置了标志,循环会执行零次,而不是一次。

据我了解Thread.MemoryBarrier(),在while循环中调用这个函数会阻止我的工作线程获取缓存版本的shouldRun,并有效地防止发生无限循环。我对Thread.MemoryBarrier的理解是否正确?

存储器屏障将确保处理器不做任何重排序的读取和后其写入,使得在逻辑上是存储器访问之前是栅障实际观察到,和副反之亦然。

如果你真的想要做低锁定代码,我会倾向于使字段变得不稳定,而不是引入显式的内存屏障。 “volatile” C#语言的一个特性。一种危险且知之甚少的特征,但却是该语言的一个特征。它明确告诉代码的读者,有问题的字段将在多线程上使用而不锁定。

这是一个合理的方式来确保我的循环将停止一旦shouldRun被任何线程设置为false?

有些人会认为这是合理的。没有一个非常好的理由,我不会在自己的代码中这样做。

通常,低锁技术通过性能考虑是合理的。有两个这样的考虑因素:

首先,竞争锁可能非常慢;只要有代码在锁中执行,它就会阻塞。如果由于争用太多而导致性能问题,那么我会首先尝试通过消除争用来解决问题。只有当我无法消除争论时,我才会采取低锁定技术。

二,可能是一个无锁锁太慢。如果您在循环中所做的“工作”比如说少于200纳秒,那么检查无锁锁定所需的时间(大约20纳秒)是完成工作所花时间的很大一部分。在这种情况下,我会建议你每循环做更多的工作。循环是否真的有必要在设置控制标志的200 ns内停止?

只有在最极端的性能情况下,我会想象检查无锁锁的成本是程序中花费的时间的很大一部分。

而且,当然,如果您每隔200 ns就会产生一次内存障碍,那么您也可能会以其他方式破坏性能。处理器希望为您提供那些移动内存访问 - 即时优化;如果你迫使它不断放弃这些优化,那么你错过了一个潜在的胜利。

2

在这种情况下Thread.MemoryBarrier();

private static volatile readonly bool shouldRun = true; 

较少高性能,但不小于安全的变体,因为如果这是必要的,以实现shouldRun挥发性的声明将执行一个存储器屏障向上最新阅读,但可能不需要这样做。

3

取决于你正在尝试做的,这可能不是你所期望的。从MSDN,内存屏障:

同步存储器访问如下:在执行 当前线程的处理器不能重新排序以这样的方式操作的指令存储器 之前调用内存屏障存储器 之后执行存取后面的呼叫接入到MemoryBarrier。

这将防止指令重新排序的内存屏障调用之前之后,但不会阻止线程调度从决定该循环应该有另一个去圆作家线程实际执行写,例如前它不是一个锁定或同步机制。它只是防止循环中的其他指令(如变量初始化)在之前被重新排序以检查shouldRun的值。

同样,离开了这一点,不会造成在两种情况下无限环 - ,shouldRun将与每个迭代检查。这里没有“缓存值”。

+0

只要没有特定的同步时间,线程就会发现应该运行失败,根本就不需要任何屏蔽。田间值将被观察到最终改变;如果为了发生正确的行为而存在特定的读取和写入所需的排序/可观察性,则栅栏只是一个问题。 – 2012-01-04 16:02:50

7

我相信你的理解是有点过了这条线

据我了解Thread.MemoryBarrier(),具有while循环这个调用会阻止我的工作线程获取的缓存版本shouldRun,并有效地防止发生无限循环。

内存屏障是强制执行读/写指令的排序约束的一种方式。虽然读取/写入重新排序的结果可能具有缓存内存屏障的外观,但实际上并未以任何方式影响缓存。它只是读写指令不能跨越的栅栏。

这很可能不会阻止无限循环。什么是内存栅栏做的是这种情况迫使所有的读取和发生在shouldRun前值出现在循环体写由循环条件

Wikipedia has a nice walk through on memory barrier that you may find useful

3

这不是对问题的直接回答;但是,上述帖子似乎已经涵盖了这一点。似乎没有明确说明的是如何达到预期的结果。实在是没有错的只是使用预期的线程同步对象:

private static readonly ManualResetEvent shutdownEvent = new ManualResetEvent(false); 

//Thread 1 - continue until event is signaled 
while(!shutodwnEvent.WaitOne(0)) 
{ 
    // Do some work .... 
    Thread.MemoryBarrier(); 
} 

//Thread 2 - signal the other thread 
shutdownEvent.Set(); 

另一种方法是使用访问变量时,锁声明:

private static object syncRoot = new Object(); 
private static bool shouldRun = true; 

//Thread 1 
bool bContinue; 
lock(syncRoot) 
    bContinue = shouldRun; 

while(bContinue) 
{ 
    // Do some work .... 
    lock(syncRoot) 
     bContinue = shouldRun; 
} 

//Thread 2 
lock(syncRoot) 
    shouldRun = false;