相信最近看过我的文章的朋友对于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;}
经验总结扩展阅读
- 2023年9月16日买宠物好不好 2023年9月16日买宠物行吗
- 2023年9月16日买鸡好吗 2023年9月16日买鸡行吗
- 2023年农历八月初二买鱼吉日 2023年9月16日买鱼吉日一览表
- 2023年农历八月初二宜买鸭吗 2023年9月16日买鸭好吗
- 圣诞节祝福问候语
- 2023年9月16日买牛吉日一览表 2023年9月16日买牛好吗
- 游乐场的万圣节祝福语
- 为什么大部分冰箱都是从左往右开门 冰箱是怎么发明出来的
- 五险一金到底是怎么算的 离职后怎么处理五险一金
- 2023天气太热幽默朋友圈文案怎么发 表达天气热到爆炸的搞笑句子说说