写入/读取何时影响主存储器?

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,等等).

  • Volatile甚至不能保证"及时"位 - 只是"按顺序".我已经决定我不能正确理解易变性......整个内存模型太混乱了.当它使用像PFX这样的另一个框架时,我只会进行无锁编码... (6认同)
  • 关于`volatile`的注释:当读取跟随写入时,不保证这个"顺序",因此使得"volatile"不像人们所期望的那样完美.这似乎是一个鲜为人知的事实,但却是灾难的保证.其中,这里解释如下:http://www.albahari.com/threading/part4.aspx#_Memory_Barriers (3认同)

Abe*_*bel 3

易失性和互锁已经被提及,您要求原语,列表中的一项补充是Thread.MemoryBarrier()在写入或读取之前使用。这保证了内存写入和读取不会重新排序。

这是“手动”做的事情lockInterlocked并且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)

  • 是的。我不知道它是否真的使 CPU 缓存无效,但可以保证当您从该地址读取时(如果读取位于 MemoryBarrier 之后),写入该地址的任何指令都会完成。这是一个很好的补充读物,以更一般的术语解释了这一原理:http://en.wikipedia.org/wiki/Memory_barrier (2认同)