在Link.TryAdd
方法的dapper代码中,有以下代码:
var snapshot = Interlocked.CompareExchange(ref head, null, null);
为什么这需要而不是简单:
var snapshot = head;
这两行都不会更改head
的值,两行都会将head
的值分配给snapshot
。为什么在第二个选择第一个?
修改:我所指的代码在这里:https://github.com/SamSaffron/dapper-dot-net/blob/77227781c562e65c167bf7a933d69291d5bdc6f3/Dapper/SqlMapper.cs
答案 0 :(得分:7)
他们想要进行易失性读取,但是Thread.VolatileRead
没有带有泛型类型参数的重载。使用Interlocked.CompareExchange
这种方式可以获得相同的结果。
他们试图解决的问题是,如果认为合适,JIT编译器可以优化分配给temp。如果另一个线程在当前线程在一系列操作中使用头部引用时,这会导致线程问题。
编辑:
问题不是在TryAdd
开头读取陈旧值。问题是,在第105行,他们需要将当前头部与前一个头部进行比较(保持在snapshot
)。如果存在优化,则没有snapshot
变量保留先前的值,并且此时再次读取head
。 CompareExchange
很可能成功,即使行103和105之间的头可能已经改变。结果是如果两个线程同时调用TryAdd
,则列表中的节点将丢失。
答案 1 :(得分:2)
mike z是对的:这阻止了(合法的)JIT优化会破坏代码。
他们本可以使用 volatile struct trick :读取head
并将其分配给某个struct的volatile字段。接下来,从该字段中读取它,并保证它是易失性读取!
结构本身根本不重要。重要的是,使用volatile字段来复制变量。
就像那样:
struct VolatileHelper<T> { public volatile T Value; }
...
var volatileHelper = new VolatileHelper<Field>();
volatileHelper.Value = head;
var snapshot = volatileHelper.Value;
希望它没有运行时成本。在任何情况下,成本都低于导致CPU内存一致性流量的互锁操作。
实际上,每个缓存访问(甚至是读取缓存)需要内存一致性流量这一事实使得这个缓慢缓存!互锁操作是一种系统全局资源,不能随着更多CPU扩展。互锁访问使用全局硬件锁(每个内存地址,但这里只有一个地址)。