我知道 .NET 内存模型(在 .NET Framework 上;not Compact/micro/silverlight/mono/xna/what-have-you)保证对于某些类型(最显着的是原始整数和引用)操作保证是原子的。

此外,我相信 x86/x64 测试和设置指令(以及 Interlocked.CompareExchange)实际上引用了全局内存位置,所以如果它成功另一个 Interlocked.CompareExchange 会看到新的值。

最后,我相信, volatile 关键字是一个指令 编译器 尽快传播读取和写入,并且不重新排序与此变量相关的操作(对吗?)。

这引出了几个问题:

  1. 我的上述信念正确吗?
  2. Interlocked.Read 没有 int 的重载,只有 long 的重载(它们是 2 个字,因此通常不会以原子方式读取)。我总是假设 .NET 内存模型保证在读取整数/引用时看到最新的值,但是对于处理器缓存、寄存器等。我开始发现这可能是不可能的。那么有没有办法强制重新获取变量呢?
  3. 对于整数和引用来说,挥发性足以解决上述问题吗?
  4. 在 x86/x64 上我可以假设...

如果有两个全局整型变量 x 和 y,都初始化为 0,如果我写:

x = 1;
y = 2;

没有线程会看到 x = 0 和 y = 2 (即写入将按顺序发生)。如果它们不稳定,这会改变吗?

有帮助吗?

解决方案

  • 只有对最多 32 位宽(在 x64 系统上为 64 位宽)的变量的读取和写入才是原子的。所有这一切都意味着你不会阅读 整数 并得到半写值。这并不意味着算术是原子的。
  • 互锁操作也充当内存屏障,所以是的, Interlocked.CompareExchange 将看到更新后的值。
  • 这一页. 。易变并不意味着有序。一些编译器可能会选择不对易失性变量的操作重新排序,但 CPU 可以自由地重新排序。如果要阻止 CPU 对指令重新排序,请使用(完整)内存屏障。
  • 内存模型确保读取和写入是原子的,并且使用 volatile 关键字确保读取将 总是 来自内存,而不是来自寄存器。那么你 将要 查看最新值。这是因为 x86 CPU 会在适当的时候使缓存失效 - 请参阅 . 。另请参阅 InterlockedCompareExchange64 了解如何自动读取 64 位值。
  • 最后,最后一个问题。答案是一个线程实际上可以看到 x = 0y = 2, ,并且使用 volatile 关键字不会改变这一点,因为 CPU 可以自由地重新排序指令。你需要一个记忆屏障。

概括:

  1. 编译器可以自由地重新排序指令。
  2. CPU 可以自由地重新排序指令。
  3. 字大小的读取和写入是原子的。算术和其他操作不是原子的,因为它们涉及读取、计算,然后写入。
  4. 从内存中读取字大小的数据将始终检索最新值。但大多数时候你不知道自己是否真的在凭记忆阅读。
  5. 完整的内存屏障会停止 (1) 和 (2)。大多数编译器允许您自行停止 (1)。
  6. 易失性关键字确保您从内存中读取 - (4)。
  7. 互锁操作(锁前缀)允许多个操作是原子的。例如,读+写(InterlockedExchange)。或者读+比较+写(InterlockedCompareExchange)。它们还充当内存屏障,因此(1)和(2)被停止。它们总是写入内存(显然),因此(4)是可以保证的。

其他提示

遇到了这个旧线程。Hans 和 wj32 的答案都是正确的,除了关于 volatile.

具体针对你的问题

在 x86/x64 上我可以假设...如果 有两个全局整数变量 x 和 y,两者都初始化为 0,如果 我写: x = 1; y = 2;

没有线程会看到 x = 0 和 y = 2(即写入将 按顺序发生)。如果出现以下情况,这是否会改变 它们是否易变?

如果 y 是易失性的,写入 x 保证在写入之前发生 y, ,因此没有线程会看到 x = 0y = 2. 。这是因为对易失性变量的写入具有“释放语义”(逻辑上相当于释放栅栏的发射),即在它不会移动之前的所有读/写指令都会通过它。(这意味着如果 x 是不稳定的,但 y 不是,您可能仍然会看到意外的情况 x = 0y = 2.) 请参阅中的描述和代码示例 C# 规范 更多细节。

不,易失性关键字和原子性保证太弱了。你需要一个内存屏障来确保这一点。您可以使用 Thread.MemoryBarrier() 方法显式获取一个。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top