C#如何保证读/写操作的原子性?

时间:2010-01-16 06:49:10

标签: c# atomic

C#规范在第5.5节中说明了对某些类型的读写(即boolcharbytesbyteshort,{ {1}},ushortuintint和引用类型)保证是原子的。

这激起了我的兴趣。你怎么能这样做?我的意思是,如果我希望读取和写入看起来是原子的,那么我个人的低级体验只会让我锁定变量或使用障碍;如果必须为每一次读/写操作完成,那将是一个性能杀手。然而,C#做了类似的事情。

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

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

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

5 个答案:

答案 0 :(得分:15)

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

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

答案 1 :(得分:5)

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

值得注意的是CLR保证不是很好。对齐承诺不足以编写对双精度数组一致的代码。在this thread中很好地证明了这一点。因此,与使用SIMD指令的机器代码互操作也非常困难。

答案 2 :(得分:4)

在x86上,读取和写入都是原子的。它在硬件级别受支持。然而,这并不意味着加法和乘法等操作是原子的;他们需要加载,计算,然后存储,这意味着他们可以干涉。这就是锁定前缀的来源。

你提到了锁定和记忆障碍;它们与读取和写入是原子的没有任何关系。在使用或不使用内存屏障的x86上,你将看不到半写的32位值。

答案 3 :(得分:3)

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

现在,无论是C#还是Java,还是它们运行的​​处理器,以及哪些都很昂贵,都会发出内存障碍,以便这些变量可以用于多线程程序中的同步。但是,在Java和C#中,您可以使用“volatile”属性标记变量,在这种情况下,编译器会负责发出适当的内存障碍。

答案 4 :(得分:-4)

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