5 c# multithreading memory-model memory-fences lock-free
当我在一个字段中写入一个值时,在将新值保存在主内存中时,我能得到什么保证?例如,我怎么知道处理器没有将新值保留在它的私有缓存中,而是更新了主内存?
另一个例子:
int m_foo;
void Read() // executed by thread X (on processor #0)
{
Console.Write(m_foo);
}
void Write() // executed by thread Y (on processor #1)
{
m_foo = 1;
}
Run Code Online (Sandbox Code Playgroud)
是否有可能在Write()完成执行后,其他一些线程执行Read()但实际上会看到"0"作为当前值?(因为之前对m_foo的写入可能还没有刷新?).
什么样的原语(除了锁)可用于确保写入被刷新?
编辑
在我使用的代码示例中,写入和读取以不同的方法放置.Thread.MemoryBarrier不会影响同一范围内存在的指令重写吗?
另外,假设它们不会被JIT内联,我怎样才能确保写入m_foo的值不会存储在寄存器中,而是存储在主存中?(或者,当读取m_foo时,它不会从CPU缓存中获取旧值).
是否可以在不使用锁或'volatile'关键字的情况下实现此目的?(另外,假设我没有使用原始类型,但是WORD大小的结构[不能应用那么易变].)
Mar*_*ell 11
如果你想确保它被及时和有序地写入,那么将其标记为volatile,或者(更多的痛苦)使用Thread.VolatileRead/ Thread.VolatileWrite(不是一个有吸引力的选项,很容易错过一个,使它无用).
volatile int m_foo;
Run Code Online (Sandbox Code Playgroud)
否则你几乎没有任何保证(只要你说多个线程).
你可能也想看看锁定(Monitor)或Interlocked,这将达到同样的效果,只要将相同的方法从所有接入的信息(即所有lock或全部Interlocked,等等).
易失性和互锁已经被提及,您要求原语,列表中的一项补充是Thread.MemoryBarrier()在写入或读取之前使用。这保证了内存写入和读取不会重新排序。
这是“手动”做的事情lock,Interlocked并且volatile大多数时候可以自动完成。你可以用它来完全替代任何其他技术,但它可以说是最难走的路,MSDN 是这样说的:
“使用 MemoryBarrier 构建正确的多线程程序很困难。对于大多数用途,C# lock 语句、Visual Basic SyncLock 语句和 Monitor 类的方法提供了更简单且不易出错的方法来同步内存访问。我们建议你使用它们而不是 MemoryBarrier。”
VolatileRead一个非常好的例子是和的实现VolatileWrite,它们都在内部使用MemoryBarrier。要遵循的基本经验法则是:读取变量时,在读取后放置内存屏障。当您写入值时,内存屏障必须出现在写入之前。
如果您怀疑这是否效率较低lock,请考虑锁定只不过是“完全防护”,因为它在代码块之前和之后放置了内存屏障(暂时忽略监视器)。Albahari在这篇关于线程、锁定、易失性和内存屏障的优秀权威文章中很好地解释了这一原理。
从反射镜:
public static void VolatileWrite(ref byte address, byte value)
{
MemoryBarrier();
address = value;
}
public static byte VolatileRead(ref byte address)
{
byte num = address;
MemoryBarrier();
return num;
}
Run Code Online (Sandbox Code Playgroud)