.NET 内存模型、易失性变量以及测试和设置:保证什么?

发布于 2024-08-18 15:34:16 字数 704 浏览 13 评论 0原文

我知道.NET内存模型(在.NET框架上;不是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 (即写入将发生在命令)。如果它们不稳定,这会改变吗?

I know that the .NET memory model (on the .NET Framework; not compact/micro/silverlight/mono/xna/what-have-you) guaranteed that for certain types (most notably primitive integers and references) operations were guaranteed to be atomic.

Further, I believe that the x86/x64 test-and-set instruction (and Interlocked.CompareExchange) actually references the global memory location, so if it succeeds another Interlocked.CompareExchange would see the new value.

Finally, I believe that the volatile keyword is an instruction to the compiler to propagate reads & writes ASAP and to not reorder operations concerning this variable (right?).

This leads to a few questions:

  1. Are my beliefs above correct?
  2. Interlocked.Read does not have an overload for int, only for longs (which are 2 WORDs and thus are not normally read atomically). I always assumed that the .NET memory model guaranteed that the newest value would be seen when reading ints/references, however with processor caches, registers, etc. I'm starting to see this may not be possible. So is there a way to force the variable to be re-fetched?
  3. Is volatile sufficient to solve the above problem for integers and references?
  4. On x86/x64 can I assume that...

If there are two global integer variables x and y, both initialized to 0 that if I write:

x = 1;
y = 2;

That NO thread will see x = 0 and y = 2 (i.e. the writes will occur in order). Does this change if they are volatile?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

执着的年纪 2024-08-25 15:34:16
  • 只有对最多 32 位宽(在 x64 系统上为 64 位宽)的变量的读取和写入才是原子的。所有这些意味着您不会读取 int 并获得半写入的值。这并不意味着算术是原子的。
  • 互锁操作还充当内存屏障,因此,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)是可以保证的。
  • Only reads and writes to variables that are at most 32 bits wide (and 64 bits wide on x64 systems) are atomic. All this means is that you won't read an int and get a half-written value. It doesn't mean arithmetic is atomic.
  • The interlocked operations also act as memory barriers, so yes, Interlocked.CompareExchange will see the updated value.
  • See this page. Volatile does not mean ordered. Some compilers may choose not to re-order operations on volatile variables, but the CPU is free to re-order. If you want to stop the CPU from re-ordering instructions, use a (full) memory barrier.
  • The memory model ensures that reads and writes are atomic, and using the volatile keyword ensures that reads will always come from memory, not from a register. So you will see the newest value. This is because x86 CPUs will invalidate the cache when appropriate - see this and this. Also, see InterlockedCompareExchange64 for how to atomically read 64-bit values.
  • And finally, the last question. The answer is a thread could in fact see x = 0 and y = 2, and using the volatile keyword doesn't change that because the CPU is free to re-order instructions. You need a memory barrier.

Summary:

  1. The compiler is free to re-order instructions.
  2. The CPU is free to re-order instructions.
  3. Word-sized reads and writes are atomic. Arithmetic and other operations are not atomic because they involve a read, compute, then write.
  4. Word-sized reads from memory will always retrieve the newest value. But most of the time you don't know if you're actually reading from memory.
  5. A full memory barrier stops (1) and (2). Most compilers allow you to stop (1) by itself.
  6. The volatile keyword ensures you're reading from memory - (4).
  7. The interlocked operations (the lock prefix) allow multiple operations to be atomic. For example, a read + write (InterlockedExchange). Or a read + compare + write (InterlockedCompareExchange). They also act as memory barriers, so (1) and (2) are stopped. They always write to memory (obviously), so (4) is ensured.
不寐倦长更 2024-08-25 15:34:16

遇到了这个旧线程。 Hans 和 wj32 的答案都是正确的,除了关于 易失性 的部分。

具体针对你的问题

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

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

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

Came across this old thread. The answers from Hans and wj32 are all correct except for the part regarding volatile.

Specifically regarding your question

On x86/x64 can I assume that... If
there are two global integer variables
x and y, both initialized to 0 that if
I write:
x = 1; y = 2;

That NO thread will see
x = 0 and y = 2 (i.e. the writes will
occur in order). Does this change if
they are volatile?

If y is volatile, the write to x is guarantee to happen before the write to y, therefore no thread will ever see x = 0 and y = 2. That is because the write to a volatile variable has the "release semantic" (logically equivalent to the emission of a release fence), i.e. all read/write instructions before it won't move pass it. (This implies that if x is volatile but y is not, you might still see the unexpected x = 0 and y = 2.) See the description & code example in the C# spec for more details.

居里长安 2024-08-25 15:34:16

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

No, the volatile keyword and the atomicity guarantee are much too weak. You need a memory barrier to ensure that. You can get one explicitly with the Thread.MemoryBarrier() method.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文