从ObjectPool到CAS指令

相信最近看过我的文章的朋友对于Microsoft.Extensions.ObjectPool不陌生;复用、池化是在很多高性能场景的优化技巧,它能减少内存占用率、降低GC频率、提升系统TPS和降低请求时延 。
那么池化和复用对象意味着同一时间会有多个线程访问池 , 去获取和归还对象,那么这肯定就有并发问题 。那ObjectPool在涉及多线程访问资源应该怎么做到线程安全呢?
今天就带大家通过学习ObjectPool的源码聊一聊它是如何实现线程安全的 。
源码解析ObjectPool的关键就在于两个方法,一个是Get用于获取池中的对象,另外就是Return用于归还已经使用完的对象 。我们先来简单的看看ObjectPool的默认实现DefaultObjectPool.cs类的内容 。
私有字段先从它的私有变量开始,下面代码中给出,并且注释了其作用:
// 用于存放池化对象的包装数组 长度为构造函数传入的max - 1// 为什么 -1 是因为性能考虑把第一个元素放到 _firstItem中private protected readonly ObjectWrapper[] _items;// 池化策略 创建对象 和 回收对象的防范private protected readonly IPooledObjectPolicy<T> _policy;// 是否默认的策略 是一个IL优化 使编译器生成call 而不是 callvirtprivate protected readonly bool _isDefaultPolicy;// 因为池化大多数场景只会获取一个对象 为了性能考虑 单独整一个对象不放在数组中// 避免数组遍历private protected T? _firstItem;// 这个类是在2.1中引入的,以尽可能地避免接口调用 也就是去虚拟化 callvirtprivate protected readonly PooledObjectPolicy<T>? _fastPolicy;构造方法另外就是它的构造方法,默认实现DefaultObjectPool有两个构造函数,代码如下所示:
/// <summary>/// Creates an instance of <see cref="DefaultObjectPool{T}"/>./// </summary>/// <param name="policy">The pooling policy to use.</param>public DefaultObjectPool(IPooledObjectPolicy<T> policy): this(policy, Environment.ProcessorCount * 2){// 从这个构造方法可以看出,如果我们不指定ObjectPool的池大小// 那么池大小会是当前可用的CPU核心数*2}/// <summary>/// Creates an instance of <see cref="DefaultObjectPool{T}"/>./// </summary>/// <param name="policy">The pooling policy to use.</param>/// <param name="maximumRetained">The maximum number of objects to retain in the pool.</param>public DefaultObjectPool(IPooledObjectPolicy<T> policy, int maximumRetained){_policy = policy ?? throw new ArgumentNullException(nameof(policy));// 是否为可以消除callvirt的策略_fastPolicy = policy as PooleObjectPolicy<T>;// 如上面备注所说 是否为默认策略 可以消除callvirt_isDefaultPolicy = IsDefaultPolicy();// 初始化_items数组 容量还剩一个在 _firstItem中_items = new ObjectWrapper[maximumRetained - 1];bool IsDefaultPolicy(){var type = policy.GetType();return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(DefaultPooledObjectPolicy<>);}}Get 方法如上文所说,Get()方法是ObjectPool中最重要的两个方法之一,它的作用就是从池中获取一个对象,它使用了CAS近似无锁的指令来解决多线程资源争用的问题 , 代码如下所示:
public override T Get(){// 先看_firstItem是否有值// 这里使用了 Interlocked.CompareExchange这个方法// 原子性的判断 _firstItem是否等于item// 如果等于那把null赋值给_firstItem// 然后返回_firstItem对象原始的值反之就是什么也不做var item = _firstItem;if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item){var items = _items;// 遍历整个数组for (var i = 0; i < items.Length; i++){item = items[i].Element;// 通过原子性的Interlocked.CompareExchange尝试读取一个元素// 读取成功则返回if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item){return item;}}// 如果遍历整个没有获取到元素// 那么走创建方法 , 创建一个item = Create();}return item;}上面代码中,有一个点解释一下Interlocked.CompareExchange(ref _firstItem, null, item) != item,其中!=item,如果其等于item就说明交换成功了,当前线程获取到_firstItem元素的期间没有其它线程修改_firstItem的值 。
Return 方法Retrun(T obj)方法是ObjectPool另外一个重要的方法,它的作用就是当程序代码把从池中获取的对象使用完以后,将其归还到池中 。同样,它也使用CAS指令来解决多线程资源争用的问题 , 代码如下所示:

推荐阅读