从ObjectPool到CAS指令( 二 )


public override void Return(T obj){// 使用策略的Return方法对元素进行处理// 比如 List<T> 需要调用Claer方法清除集合内元素// StringBuilder之类的也需要调用Claer方法清除缓存的字符if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj))){// 先尝试将归还的元素赋值到 _firstItem中if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, obj, null) != null){var items = _items;// 如果 _firstItem已经存在元素// 那么遍历整个数组空间 找一个存储为null的空位将对象存储起来for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i){}}}}从核心的Get()Set()方法来看,其实整个代码是比较简单的,除了有一个_firstItem有一个简单的优化 , 其余没有什么特别的复杂的逻辑 。
主要的关键就在Interlocked.CompareExchange方法上,我们在下文来仔细研究一下这个方法 。
关于 Interlocked.CompareExchangeInterlocked.CompareExchange它实际上是一个CAS的实现,也就是Compare And Swap,从名字就可以看出来,它就是比较然后交换的意思 。
从下面的代码段我们也可以看出来,它总共需要三个参数 。其特性就是只有当localtion1 == comparand的时候才会将value赋值给localtion1 , 另外吧localtion1的原始值返回出来,这些操作都是原子性的 。
// localtion1 需要比较的引用A// value 计划给引用A 赋的值// comparand 和引用A比较的引用public static T CompareExchange<T> (ref T location1, T value, T comparand)where T : class;一个简单的流程如下所示:

从ObjectPool到CAS指令

文章插图
简单的使用代码如下所示:
var a = 1;// a == 1的话就将其置为0// 判断是否成功就看返回的值是否为a的原始值if(Interlocked.CompareExchange(ref a, 0, 1) == 1) Console.WriteLine("1.成功");// 现在a已经变为0 这个交换不会成功if(Interlocked.CompareExchange(ref a, 0, 1) == 1) Console.WriteLine("2.成功");结果如下所示,只有当a的原始值为1的时候,才会交换成功:
从ObjectPool到CAS指令

文章插图
那么Interlocked.CompareExchange是如何做到原子性的?在多核CPU中,数据可能在内存或者L1、L2、L3中(如下图所示),我们如何保证能原子性的对某个数据进行操作?
从ObjectPool到CAS指令

文章插图
实际上这是CPU提供的功能,如果查看过JIT编译的结果,可以看到CompareExchange是由一条叫lock cmpxchgl的汇编指令支撑的 。
从ObjectPool到CAS指令

文章插图
其中lock是一个指令前缀,汇编指令被lock修饰后会成为"原子的",lock指令有两种实现方法:
  • 早期 - Pentium时代(锁总线),在Pentium及之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大 。
  • 现在 - P6以后时代(锁缓存),在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低lock前缀指令的执行开销 。
现在这里的锁缓存(Cache Locking)就是用了Ringbus + MESI协议 。
MESI协议是 Cacheline 四种状态的首字母的缩写,分别是修改(Modified)态、独占(Exclusive)态、共享(Shared)态和失效(Invalid)态 。Cache 中缓存的每个 Cache Line 都必须是这四种状态中的一种 。
修改态(Modified) , 如果该 Cache Line 在多个 Cache 中都有备份,那么只有一个备份能处于这种状态,并且“dirty”标志位被置上 。拥有修改态 Cache Line 的 Cache 需要在某个合适的时候把该 Cache Line 写回到内存中 。但是在写回之前,任何处理器对该 Cache Line在内存中相对应的内存块都不能进行读操作 。Cache Line 被写回到内存中之后,其状态就由修改态变为共享态 。
独占态(Exclusive),和修改状态一样 , 如果该 Cache Line 在多个 Cache 中都有备份,那么只有一个备份能处于这种状态,但是“dirty”标志位没有置上 , 因为它是和主内存内容保持一致的一份拷贝 。如果产生一个读请求,它就可以在任何时候变成共享态 。相应地,如果产生了一个写请求,它就可以在任何时候变成修改态 。

推荐阅读