快速提问。低于原子分配:
object [] table = new object[10]
...
table[3] = 10; //is it atomic ?
...
是的,赋值是原子的,因为 object 是引用类型。
正如下面从 MSDN 引用中提到的,对于大多数(不是全部)内置值类型(如下所述)和引用类型来说,它们都是原子的。CLI 保证对处理器自然指针大小(或更小)的值类型变量的读取和写入是原子的;如果您在 64 位版本的 CLR 中的 64 位操作系统上运行 C# 代码,那么 64 位双精度和长整数的读写也保证是原子的。C# 语言不保证这一点,但运行时规范可以。
MSDN 说什么
以下数据类型的读写是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float 和引用类型。此外,上一个列表中具有基础类型的枚举类型的读取和写入也是原子的。其他类型的读取和写入,包括 long、ulong、double 和 decimal,以及用户定义的类型,不保证是原子的。除了为此目的设计的库函数之外,不能保证原子读-修改-写,例如在递增或递减的情况下。
在这里可以看到埃里克本人的非常详细的回答
在谈论原子性时,我假设 OP 意味着“线程安全”。
仅当另一个线程可能读取部分写入的值时,写入操作才不是原子的。
如果object [] table
是本地方法,每个线程将获得自己的表,因此对表的任何操作都是原子的。
展望未来,我假设它table
是跨线程共享的。
OP 已将 table 定义为object
. 因此table[3] = 10
涉及拳击。
即使table[3] = 10
代表一条指令链,此操作也是原子操作,因为这最终将写入装箱实例的“地址”(当前的 CLR 实现确实使用内存地址表示对象引用)并且地址是机器的自然字长(即 32 位机器上的 32 位和 64 位机器上的 64 位)。请注意,尽管上述解释是基于当前的 CLR 实现,但规范保证了参考编写的原子性。装箱操作和装箱实例本身是线程本地的,因此其他线程无法干预。即使值超过字长(例如Decimal
) 正在编写,操作将是原子的(由于装箱)。这里必须注意,只有当正在写入的值已经以线程安全的方式获得时,上述参数才成立。
如果不涉及装箱,那么通常的字长写入规则(或由于内存对齐而小于字长)是原子的。
我严重怀疑整个术语table[3] = 10;
是原子的。虽然对某些数据类型的单次读取或写入是原子数组访问,但很可能不是。
您的示例的 IL 代码是
IL_0001: ldc.i4.s 0A
IL_0003: newarr System.Object
IL_0008: stloc.0 // table
IL_0009: ldloc.0 // table
IL_000A: ldc.i4.3
IL_000B: ldc.i4.s 0A
IL_000D: box System.Int32
IL_0012: stelem.ref
您可以看到索引在 IL_000A 处加载,然后在 IL_000D 处对 int 进行装箱,最后将装箱后的值存储在 IL_0012 处。
这些指令中的每一个都可能是原子的,但这并不意味着生成的指令链就是原子的table[3] = 10;
。