什么Interlocked.CompareExchange用于dapper .net方法?

时间:2012-06-19 21:51:30

标签: c# .net thread-safety dapper

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

2 个答案:

答案 0 :(得分:7)

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

他们试图解决的问题是,如果认为合适,JIT编译器可以优化分配给temp。如果另一个线程在当前线程在一系列操作中使用头部引用时,这会导致线程问题。

编辑:

问题不是在TryAdd开头读取陈旧值。问题是,在第105行,他们需要将当前头部与前一个头部进行比较(保持在snapshot)。如果存在优化,则没有snapshot变量保留先前的值,并且此时再次读取headCompareExchange很可能成功,即使行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扩展。互锁访问使用全局硬件锁(每个内存地址,但这里只有一个地址)。