在Link.TryAdd
方法中的 dapper 代码中,有以下代码:
var snapshot = Interlocked.CompareExchange(ref head, null, null);
为什么这是必需的而不是简单的:
var snapshot = head;
两行都没有更改 的值head
,两行都将 的值分配head
给snapshot
。为什么选择第一个而不是第二个?
在Link.TryAdd
方法中的 dapper 代码中,有以下代码:
var snapshot = Interlocked.CompareExchange(ref head, null, null);
为什么这是必需的而不是简单的:
var snapshot = head;
两行都没有更改 的值head
,两行都将 的值分配head
给snapshot
。为什么选择第一个而不是第二个?
他们想要进行 volatile 读取,但是没有Thread.VolatileRead
采用泛型类型参数的重载。使用Interlocked.CompareExchange
这种方式可以获得相同的结果。
他们试图解决的问题是,JIT 编译器可以在认为合适的情况下优化对 temp 的赋值。如果另一个线程在当前线程在一系列操作中使用它时改变了头引用,这可能会导致线程问题。
编辑:
问题不在于在TryAdd
. 问题是在第 105 行,他们需要将当前头与前一个头(保存在 中snapshot
)进行比较。如果有优化,则没有snapshot
变量保存先前的值,并head
在该点再次读取。CompareExchange
即使 head 在第 103 行和第 105 行之间发生了变化,也很有可能成功。结果是如果两个线程TryAdd
同时调用,则列表中的一个节点会丢失。
mike z 是对的:这是防止(合法)JIT 优化会破坏代码。
不过,他们本可以使用volatile struct 技巧:读取head
并将其分配给某个结构的 volatile 字段。接下来,从该字段读取它,它保证是一个易失性读取!
结构本身根本不重要。重要的是使用了一个 volatile 字段来复制变量。
像那样:
struct VolatileHelper<T> { public volatile T Value; }
...
var volatileHelper = new VolatileHelper<Field>();
volatileHelper.Value = head;
var snapshot = volatileHelper.Value;
希望它没有运行时成本。在任何情况下,成本都低于导致 CPU 内存一致性流量的互锁操作。
Actually, the fact that every cache access (even a reading one) requires memory coherency traffic makes this a slow cache! Interlocked operations are a system global resource that does not scale with more CPUs. An Interlocked access uses a global hardware lock (per memory address, but there is only one address here).