假设我有一个控制某个循环执行的字段:
private static bool shouldRun = true;
我有一个运行的线程,其代码如下:
while(shouldRun)
{
// Do some work ....
Thread.MemoryBarrier();
}
现在,另一个线程可能会将shouldRun
设置为false
,而不使用任何同步机制。
据我了解Thread.MemoryBarrier(),在while循环中调用此函数将阻止我的工作线程获取shouldRun
的缓存版本,并有效防止无限循环发生。
我对Thread.MemoryBarrier的理解是否正确?鉴于我有可以设置shouldRun
变量的线程(这不容易改变),这是一种合理的方法来确保任何线程将shouldRun
设置为false后我的循环会停止吗?
答案 0 :(得分:26)
这是正确使用Thread.MemoryBarrier()吗?
没有。假设一个线程在循环之前设置了标志,甚至开始执行。循环仍然可以使用标志的缓存值执行一次。这是正确吗?这对我来说当然不正确。我希望如果我在第一次执行循环之前设置了标志,那么循环执行零次,而不是一次。
据我了解Thread.MemoryBarrier(),在while循环中调用此函数将阻止我的工作线程获取shouldRun的缓存版本,并有效防止无限循环发生。我对Thread.MemoryBarrier的理解是否正确?
内存屏障将确保处理器不会对读取和写入进行任何重新排序,以便实际观察到在屏障之前逻辑上的内存访问在之后,反之亦然。
如果你一直在努力做低锁代码,我倾向于使字段变得不稳定,而不是引入明确的内存屏障。 “volatile”是 C#语言的一个特性。这是一种危险且知之甚少的功能,但却是该语言的一个特色。它清楚地向代码的读者传达了所讨论的字段将在没有多线程锁定的情况下使用。
这是一种合理的方法来确保一旦任何线程将shouldRun设置为false,我的循环就会停止吗?
有些人会认为这是合理的。没有非常非常好的理由,我不会在我自己的代码中这样做。
通常,低锁技术可以通过性能考虑来证明。有两个这样的考虑因素:
首先,竞争锁定可能非常缓慢;只要锁中执行代码,它就会阻塞。如果由于争用太多而导致性能问题,那么我首先尝试通过消除争用来解决问题。只有当我无法消除争用时,我才会采用低锁技术。
其次,无竞争锁可能太慢了。如果你在循环中做的“工作”需要少于200纳秒,那么检查无竞争锁定所需的时间 - 大约20纳秒 - 是完成工作所花费的时间的很大一部分。在这种情况下,我建议你每个循环做更多的工作。是否真的有必要在控制标志设置的200 ns内停止?
只有在最极端的性能场景中,我才能想象检查无竞争锁定的成本是计划花费的时间的一小部分。
当然,如果你每200 ns左右引发一次内存屏障,你也可能在其他方面破坏性能。处理器希望为您进行那些移动内存访问 - 即时优化;如果你强迫它不断放弃那些优化,你就错过了一场潜在的胜利。
答案 1 :(得分:7)
我相信你的理解有点偏离这条线
据我了解Thread.MemoryBarrier(),在while循环中调用此函数将阻止我的工作线程获取shouldRun的缓存版本,并有效防止无限循环发生。
内存屏障是一种对读/写指令强制执行排序约束的方法。虽然读/写重新排序的结果可能具有缓存的外观,但内存屏障实际上不会以任何方式影响缓存。它只是作为读写指令无法跨越的栅栏。
很可能这不会阻止无限循环。内存栅栏正在做的是这种情况正在强制循环体中发生的所有读取和写入在循环条件读取shouldRun
的值之前发生
Wikipedia has a nice walk through on memory barrier that you may find useful
答案 2 :(得分:3)
根据您的尝试,这可能不会达到预期效果。来自MSDN,MemoryBarrier:
按如下方式同步内存访问:处理器执行 当前线程无法以内存方式重新排序指令 在调用MemoryBarrier之前访问内存后执行 跟随对MemoryBarrier的调用的访问。
这将阻止在内存屏障调用之前对指令进行重新排序,但是这不会阻止线程调度在写入器线程实际执行写入之前确定循环应该具有另一个循环。它不是锁定或同步机制。它只是阻止循环中的其他指令(如变量初始化)在检查shouldRun值之前重新排序。
同样地,将其保留不会导致无限循环 - 在任何一种情况下,都会在每次迭代时检查shouldRun。这里没有“缓存值”。
答案 3 :(得分: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();
另一种方法是在访问变量时使用lock语句:
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;
答案 4 :(得分:2)
在这种情况下,Thread.MemoryBarrier();
是性能较差但不太安全的
private static volatile readonly bool shouldRun = true;
因为如果需要实现最新的读取,那么将shouldRun声明为volatile会执行内存屏障,但可能不需要这样做。