5

当我将值写入字段时,关于新值何时保存在主存储器中,我能得到什么保证?例如,我怎么知道处理器没有将新值保存在它的私有缓存中,而是更新了主内存?
另一个例子:

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;
}

是否有可能在Write()完成执行后,其他一些线程执行Read()但实际上会将“0”视为当前值?(因为也许之前对 m_foo 的写入还没有刷新?)。
什么样的原语(除了锁)可用于确保写入被刷新?


编辑
在我使用的代码示例中,写入和读取放置在不同的方法中。Thread.MemoryBarrier 不只影响存在于同一范围内的指令重新排序吗?

另外,假设它们不会被 JIT 内联,我如何确保写入 m_foo 的值不会存储在寄存器中,而是存储到主存储器中?(或者当 m_foo 被读取时,它不会从 CPU 缓存中获取旧值)。

是否可以在不使用锁或“volatile”关键字的情况下实现这一目标?(另外,假设我没有使用原始类型,而是使用 WORD 大小的结构[因此无法应用 volatile]。)

4

4 回答 4

11

如果您想确保它及时且有序地编写,则将其标记为volatile,或者(更痛苦)使用Thread.VolatileRead/ Thread.VolatileWrite(不是一个有吸引力的选项,并且容易错过一个,使其无用)。

volatile int m_foo;

否则,您几乎无法保证任何事情(只要您谈论多个线程)。

您可能还想查看锁定 ( Monitor) 或,只要从所有访问(即 all或 all等)使用相同的方法Interlocked,它就会达到相同的效果。lockInterlocked

于 2009-11-14T13:14:26.080 回答
3

Volatile 和 Interlocked 已经提到过,您要求提供原语,列表中的一项补充是Thread.MemoryBarrier()在您的写入或读取之前使用。这保证不会对内存写入和读取进行重新排序。

这是“手动”做的事情lockInterlocked并且volatile大部分时间都可以自动完成。您可以将其用作任何其他技术的完全替代品,但它可以说是最困难的旅行路径,MSDN 是这样说的:

“使用 MemoryBarrier 很难构建正确的多线程程序。对于大多数用途,C# lock 语句、Visual Basic SyncLock 语句和 Monitor 类的方法提供了更简单且不易出错的方法来同步内存访问。我们建议你使用它们而不是 MemoryBarrier。”

如何使用内存屏障

一个很好的例子是 和 的实现VolatileReadVolatileWrite它们都在内部使用MemoryBarrier。要遵循的基本经验法则是:读取变量时,在读取放置内存屏障。写入值时,内存屏障必须写入之前。

如果您怀疑这是否效率较低lock,请考虑锁定只不过是“完全围栏”,因为它在代码块之前和之后放置了一个内存屏障(暂时忽略 Monitor)。这个原理在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;
}
于 2009-11-14T13:29:50.073 回答
2

只要您不使用任何同步,您就无法保证在一个处理器上运行的线程会看到在另一个处理器上运行的另一个线程所做的更改。这是因为该值可以缓存在 CPU 缓存或 CPU 寄存器中。

因此,您需要将变量标记为 volatile。这将在读取和写入之间创建一个“发生前”的关系。

于 2009-11-14T13:19:41.007 回答
2

这不是处理器缓存问题。写入通常是直通的(写入同时进入缓存和主内存),所有读取都将访问缓存。但是还有许多其他缓存(编程语言、库、操作系统、I/O缓冲区等)。编译器还可以选择将变量保留在处理器寄存器中,并且从不将其写入主存储器(这就是 volatile 运算符的设计目的,当它可以是内存映射 I/O 时避免将值存储在寄存器中)。

如果您有多个进程或多个线程并且同步是一个问题,您必须明确地进行,根据用例有很多方法可以做到这一点。

对于单线程程序,不要在意,编译器将执行它必须执行的操作,读取将访问已写入的内容。

于 2009-11-14T13:23:23.263 回答