MSIL指令是否具有原子性?

10

我正在使用MSIL反编译器-ILDASM进行一些实验,并尝试反编译一个简单的.NET方法。

操作码看起来大致如下:

.method private hidebysig static int32  Add(int32 a,
                                            int32 b) cil managed
{
  // Code size       18 (0x12)
  .maxstack  2
  .locals init ([0] int32 c,
           [1] int32 d,
           [2] int32 CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldc.i4.5
  IL_0003:  add
  IL_0004:  stloc.0
  IL_0005:  ldarg.1
  IL_0006:  ldc.i4.s   10
  IL_0008:  add
  IL_0009:  stloc.1
  IL_000a:  ldloc.0
  IL_000b:  ldloc.1
  IL_000c:  add
  IL_000d:  stloc.2
  IL_000e:  br.s       IL_0010
  IL_0010:  ldloc.2
  IL_0011:  ret
}
我想知道的是,这些操作码是否是原子性的?也就是说,在一个抢占式调度内核中,一个单独的操作码在执行完成之前是否可能被抢占?这里的操作码可以很容易地映射到汇编指令,几乎是一对一的,因为它们有单独的加载、存储、添加等操作码。
但是,对于更复杂的操作码呢?比如 "call",当操作数是一个方法引用标记时,首先应该跟随解析该方法,然后才能调用,这种情况下还是原子性吗?

这取决于处理器... - Oded
2个回答

15
不是所有的操作码都是原子的。例如,如果您使用stlocldloc来处理大于本机指针大小的值类型,则不能保证是原子的。
ECMA 335的第12.6.6节保证了这一点:
“当所有对一个位置的写操作具有相同的大小时(参见§12.6.2),符合CLI的应该保证对不大于本地字长(即类型本机int的大小)的适当对齐的存储器位置进行的读取和写入访问是原子的。原子写入不会改变除所写入的位之外的任何位。”
但是还有一个注释:
“[注:当本机int的大小为32位时,即使某些实现在数据对齐到8字节边界时执行原子操作,也没有保证对8字节数据的原子访问。 结束说明]”
因此,这意味着例如在x86上存储或读取Int64的任何操作码都不能保证是原子的...

哇!这是一个惊人的启示。这意味着,如果您有两个线程在可见范围内存储类型为“long”的变量的值,您可能期望该值不等于任何存储值。对吗?例如,线程1 { val = 1 },线程2 { val = 4294967296 },结果值可能是4294967297?因为这些值触及了两个不同的32位集?而确保存储这两个值之一的唯一方法是使用Interlocked.*吗? - Karim Agha
2
是的。Volatile 也没有帮助(它只有很少的帮助)。 - usr
@KarimA.:绝对没错。在多个线程之间共享状态而不需要任何锁定是很棘手的...尽量避免需要这样做。 - Jon Skeet
@John,但是对于整数(32位整数),我保证全局变量中存储的值将是其中之一。如果我错了,请纠正我。 - Karim Agha
1
@KarimAgha,您可以通过始终使用Interlocked.*指令来读取/写入受影响的内存位置,从而保证在32位和64位模式下都能实现原子64位获取和存储。对于.NET在32位模式下,这是获得64位无锁操作所需的正确性保证的唯一方法。 - Glenn Slayden

0

我认为原子性并没有在IL指令中被定义。它是根据从/到内存的加载和存储来定义的。

关于加载和存储的原子性规则非常复杂。它们与存储值的对齐和大小有关。

你提到的“call”示例没有意义:它不访问内存。原子性的概念与调用指令无关。


不,我在“调用”方面所问的是,在被调用的方法开始执行之前,线程是否可能被抢占。例如,在解析引用时。 - Karim Agha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接