11

boolC# 规范在第 5.5 节中规定,对某些类型(即、charbytesbyteshortushortuintint、和引用类型)的读写float保证是原子的。

这引起了我的兴趣。你怎么能那样做?我的意思是,如果我想让读写看起来是原子的,我的个人经验只是告诉我锁定变量或使用障碍;如果每次读/写都必须完成,那将是性能杀手。然而,C# 做了类似的事情。

也许其他语言(如 Java)可以做到这一点。我真的不知道。我的问题并不是真的针对特定语言,只是我知道 C# 可以做到。

我知道它可能必须处理某些特定的处理器指令,并且可能无法在 C/C++ 中使用。但是,我仍然想知道它是如何工作的。

[编辑]说实话,我相信在某些情况下读写可能是非原子的,就像一个 CPU 可以访问一个内存位置,而另一个 CPU 正在那里写入。这是否仅在 CPU 无法一次处理所有对象时发生,例如因为它太大或因为内存未在正确的边界上对齐?

4

5 回答 5

16

这些类型保证原子性的原因是它们都是 32 位或更小。由于 .NET 仅在 32 位和 64 位操作系统上运行,因此处理器架构可以在一次操作中读取和写入整个值。这与 32 位平台上的 Int64 形成对比,后者必须使用两个 32 位操作进行读写。

我不是一个真正的硬件专家,所以如果我的术语让我听起来像个小丑,我很抱歉,但这是基本想法。

于 2010-01-16T07:39:44.877 回答
5

在 x86 和 x64 内核上实现原子性保证相当便宜,因为 CLR 只承诺 32 位或更小的变量的原子性。所需要的只是变量正确对齐并且不跨越缓存行。JIT 编译器通过在 4 字节对齐的堆栈偏移上分配局部变量来确保这一点。GC 堆管理器对堆分配执行相同的操作。

值得注意的是,CLR 保证并不是一个很好的保证。对齐承诺不足以编写对双精度数组始终具有性能的代码。在这个线程中很好地展示了。由于这个原因,与使用 SIMD 指令的机器代码互操作也非常困难。

于 2010-01-16T13:09:05.540 回答
4

无论如何,在 x86 上,读写都是原子的。它在硬件级别得到支持。然而,这并不意味着像加法和乘法这样的操作是原子的。它们需要加载、计算、然后存储,这意味着它们可能会干扰。这就是锁定前缀的来源。

您提到了锁定和内存障碍;它们与原子的读写没有任何关系。在 x86 上,无论是否使用内存屏障,您都无法看到写入一半的 32 位值。

于 2010-01-16T07:39:04.347 回答
3

是的,C# 和 Java 保证某些原始类型的加载和存储是原子的,就像你说的那样。这很便宜,因为能够运行 .NET 或 JVM 的处理器确实保证适当对齐的原始类型的加载和存储是原子的。

现在,C#、Java 和它们运行的​​处理器都没有保证,而且价格昂贵,就是发布内存屏障,以便这些变量可以用于多线程程序中的同步。但是,在 Java 和 C# 中,您可以使用“volatile”属性标记变量,在这种情况下,编译器会负责发出适当的内存屏障。

于 2010-01-16T18:53:54.960 回答
-5

你不能。即使一直到汇编语言,您也必须使用特殊的 LOCK 操作码,以确保不会出现另一个内核甚至进程并消除您所有的辛勤工作。

于 2010-01-16T06:52:33.597 回答