3

Link.TryAdd方法中的 dapper 代码中,有以下代码:

var snapshot = Interlocked.CompareExchange(ref head, null, null);

为什么这是必需的而不是简单的:

var snapshot = head;

两行都没有更改 的值head,两行都将 的值分配headsnapshot。为什么选择第一个而不是第二个?

编辑:我指的代码在这里:https ://github.com/SamSaffron/dapper-dot-net/blob/77227781c562e65c167bf7a933d69291d5bdc6f3/Dapper/SqlMapper.cs

4

2 回答 2

7

他们想要进行 volatile 读取,但是没有Thread.VolatileRead采用泛型类型参数的重载。使用Interlocked.CompareExchange这种方式可以获得相同的结果。

他们试图解决的问题是,JIT 编译器可以在认为合适的情况下优化对 temp 的赋值。如果另一个线程在当前线程在一系列操作中使用它时改变了头引用,这可能会导致线程问题。

编辑:

问题不在于在TryAdd. 问题是在第 105 行,他们需要将当前头与前一个头(保存在 中snapshot)进行比较。如果有优化,则没有snapshot变量保存先前的值,并head在该点再次读取。CompareExchange即使 head 在第 103 行和第 105 行之间发生了变化,也很有可能成功。结果是如果两个线程TryAdd同时调用,则列表中的一个节点会丢失。

于 2012-06-19T22:04:10.400 回答
2

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).

于 2012-06-19T22:23:27.393 回答