编译器或处理器是否可以重新排序以下指令,以便另一个线程看到a == 0
和b == 1
?
假设int a = 0, b = 0;
某处。
System.Threading.Interlocked.CompareExchange<int>(ref a, 1, 0);
System.Threading.Interlocked.CompareExchange<int>(ref b, 1, 0);
编译器或处理器是否可以重新排序以下指令,以便另一个线程看到a == 0
和b == 1
?
假设int a = 0, b = 0;
某处。
System.Threading.Interlocked.CompareExchange<int>(ref a, 1, 0);
System.Threading.Interlocked.CompareExchange<int>(ref b, 1, 0);
不,使用Interlock
将发出完整的内存围栏信号。“也就是说,任何变量在调用方法之前写入,在Interlocked
方法之前执行Interlocked
,任何变量在调用之后读取,在调用之后执行。” [1] 他们使用易失性读/写方法来防止b = 1
之前的a = 1
.
[1]:Jeffrey Richter:“通过 C# 进行 CLR - 第三版”第 V 部分线程,第 803 页
当然可以。组成操作的各个操作CompareExchange
不能被可观察地重新排序,但是CompareExchange
从另一个线程的角度来看,两个调用可以重新排序,只要执行此代码的线程不能观察到这种行为。
现有的同步工具正在防止影响与该操作相关的内存位置CompareExchange
的操作之间可观察到的重新排序,而不是一般的任何操作,该代码中也没有任何东西可以防止编译器或 JITter完全重新排序这两个调用(来自另一个线程的视角)。CompareExchange
你读的理论太多了。是的,如果另一个线程这样做,它可能会在实践中发生
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 等较弱的内存模型打破。