7

编译器或处理器是否可以重新排序以下指令,以便另一个线程看到a == 0b == 1

假设int a = 0, b = 0;某处。

System.Threading.Interlocked.CompareExchange<int>(ref a, 1, 0);
System.Threading.Interlocked.CompareExchange<int>(ref b, 1, 0);
4

3 回答 3

6

不,使用Interlock将发出完整的内存围栏信号。“也就是说,任何变量在调用方法之前写入,在Interlocked方法之前执行Interlocked,任何变量在调用之后读取,在调用之后执行。” [1] 他们使用易失性读/写方法来防止b = 1之前的a = 1.

[1]:Jeffrey Richter:“通过 C# 进行 CLR - 第三版”第 V 部分线程,第 803 页

于 2014-08-25T20:17:25.770 回答
3

当然可以。组成操作的各个操作CompareExchange不能被可观察地重新排序,但是CompareExchange从另一个线程的角度来看,两个调用可以重新排序,只要执行此代码的线程不能观察到这种行为。

现有的同步工具正在防止影响与该操作相关的内存位置CompareExchange的操作之间可观察到的重新排序,而不是一般的任何操作,该代码中也没有任何东西可以防止编译器或 JITter完全重新排序这两个调用(来自另一个线程的视角)。CompareExchange

于 2014-08-25T20:12:10.877 回答
3

你读的理论太多了。是的,如果另一个线程这样做,它可能会在实践中发生

Console.WriteLine("a: {0}, b: {1}", a, b);

由于用于格式化字符串的 String.Format 的签名为

   String Format(string fmt, params object[] args)
   {
       ....
   }

由于拳击,您的整数将被复制。唯一需要为真的条件是线程时间片在其复制处于未初始化状态的 a 时结束。稍后当线程恢复工作时,两个变量都设置为一个,但您的控制台输出将是

a: 0, b: 1

如果您在没有意识到的情况下使用值的副本,则会立即看到其他值。这就是为什么您通常让其他人编写正确的无锁代码的原因。如果尝试使用 Console.WriteLine 调试无锁代码,我祝你好运。

尽管 a 和 b 是按顺序设置的(​​我认为 JIT 编译器不会重新排序您的联锁调用),但您不能保证其他线程只能看到两个零或两个 1。您可以尝试先读取 b 并检查它是否具有值 one 然后您可以推断出 a 的值,但在这种情况下,您甚至都不需要 a 的值。至少对于 x86 内存模型应该是这样。这种假设可以被 ARM 等较弱的内存模型打破。

于 2014-08-25T20:48:23.400 回答