news 2026/3/14 15:42:09

ZLToolKit模块(三)ResourcePool(对象池)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ZLToolKit模块(三)ResourcePool(对象池)

ResourcePool

1. 核心设计理念

ResourcePool实现了一个基于智能指针自定义删除器(Deleter)机制的高性能对象池。

其核心思想是:当用户从池中获取对象时,得到的是一个std::shared_ptr。当这个智能指针引用计数归零(离开作用域)时,不会直接delete内存,而是触发自定义删除器,将对象“归还”给对象池。

测试代码和使用示例在test_resourcePool.cpp

2. 类结构与关系分析

代码中主要涉及三个类,它们的关系如下:

2.1ResourcePool_l<C>(内部实现类)

  • 角色:这是对象池的真正实体,负责实际的内存管理、对象存储、分配和回收。
  • 继承:继承自std::enable_shared_from_this。这是为了在创建对象时,能够安全地将自身的weak_ptr传递给发出的智能指针,以便对象析构时能找到回家的路。
  • 核心成员
    • _objs:std::vector<C*>,存储空闲对象的栈。
    • _busy:std::atomic_flag,用于实现轻量级的自旋锁(Spinlock)。
    • _alloc: 对象分配器 lambda,默认使用new C()
  • 关键行为
    • Fail-fast 锁机制:在getPtrrecycle中,使用_busy.test_and_set()。如果获取锁失败(说明有其他线程正在操作池),它不会阻塞等待,而是直接new一个新对象(获取时)或直接delete对象(回收时)。这种设计牺牲了对池大小的严格控制,换取了极高的并发性能(避免线程挂起)。

2.2ResourcePool<C>(对外接口类/外观模式)

  • 角色:这是用户直接使用的类,充当ResourcePool_l代理(Facade)包装器
  • 关系ResourcePool内部持有一个std::shared_ptr<ResourcePool_l<C>> pool
  • 存在的意义
    • 生命周期管理ResourcePool_l必须以shared_ptr形式存在才能使用shared_from_this()ResourcePool隐藏了这一细节,让用户可以像使用普通栈对象一样声明ResourcePool
    • 接口转发:将obtainsetSize等调用转发给内部的pool
2.3shared_ptr_imp<C>(增强型智能指针)
  • 角色:这是ResourcePool::obtain()返回的智能指针类型。
  • 继承:继承自std::shared_ptr<C>
  • 功能
    • 它携带了一个_quit标志(atomic_bool)。
    • 提供了quit()方法,允许用户在持有对象期间,决定该对象销毁时是归还池中还是彻底删除
    • 构造函数中定义了核心的回收逻辑(Deleter)

3. 核心流程分析

A. 获取对象 (obtain/getPtr)

  1. 用户调用ResourcePool::obtain()
  2. 转发给ResourcePool_l::obtain()
  3. 调用ResourcePool_l::getPtr()
    • 尝试获取自旋锁_busy
    • 成功:如果_objs有库存,弹出并返回;否则调用_alloc()新建。释放锁。
    • 失败(竞争激烈):不等待,直接调用_alloc()新建返回。
  4. 将裸指针包装成shared_ptr_imp,并将ResourcePool_lweak_ptr传入删除器中。

B. 回收对象 (智能指针析构)

  1. shared_ptr_imp引用计数归零,触发删除器lambda
  2. 删除器检查weakPool.lock()是否能获取到ResourcePool_l的强引用。
    • 池已销毁:直接delete ptr
    • 池尚在:调用strongPool->recycle(ptr)
  3. ResourcePool_l::recycle(ptr)
    • 尝试获取自旋锁。
    • 成功:如果池未满,emplace_back入队;如果池满了,delete ptr。释放锁。
    • 失败(竞争激烈):不等待,直接delete ptr

4. 类图 (Class Diagram)

拥有 (shared_ptr)
1
1
创建 (Create)
弱引用 (weak_ptr) 用于回收
ResourcePool
- std::shared_ptr<ResourcePool_l>C<> pool
+obtain()
+obtain2()
+setSize(size)
ResourcePool_l
- std::vector<C*> _objs
- std::atomic_flag _busy
- size_t _pool_size
- std::weak_ptr<ResourcePool_l> _weak_self
+obtain()
+recycle(C* obj)
-getPtr()
shared_ptr_imp
- std::shared_ptr<atomic_bool> _quit
+quit(bool)
+shared_ptr_imp(...)
«interface»
std_enable_shared_from_this
«class»
std_shared_ptr<C>

5. 总结

  • ResourcePool 与 ResourcePool_l 的关系:是Handle-Body (句柄-实体)Proxy (代理)的关系。ResourcePool是面向用户的句柄,负责管理ResourcePool_l的生命周期;ResourcePool_l是实际干活的实体。
  • 性能特征
    • 极高并发:使用atomic_flag实现无锁(Wait-free)的尝试机制,遇到竞争立即降级为直接分配/释放,杜绝了线程阻塞。
    • 安全性:使用weak_ptr解决循环引用和悬垂指针问题。如果池先于对象销毁,对象析构时会安全地自我删除。
  • obtain vs obtain2
    • obtain返回shared_ptr_imp,功能更强(支持quit()放弃回收),但对象体积稍大(多一个_quit指针)。 *obtain2返回标准shared_ptr,更轻量,但无法中途控制是否放弃回收

6. C++

6.1 ResourcePool_l类的成员_objs,为什么使用vector类型而不是List

ResourcePool_l中使用std::vector<C *>而不是List(或std::list),主要是出于极致性能的考虑,特别是为了配合其自旋锁 (Spinlock)Fail-fast的设计策略。

1. 极小化临界区耗时 (最关键原因)

ResourcePool_l使用了std::atomic_flag(_busy) 来实现一个非阻塞的自旋锁。

  • 机制:如果test_and_set失败(锁被占用),代码不会等待,而是直接降级为newdelete对象。
  • 要求:为了提高从池中获取对象的成功率,必须极度缩短持有锁的时间

对比两种容器的操作耗时:

  • std::vector
    • pop_back(): 仅仅是将内部的size计数器减 1。这是纯 CPU 指令操作,耗时在纳秒级。
    • emplace_back(): 在预留空间足够的情况下(_objs.reserve已调用),仅仅是将指针写入数组并size加 1。也是纳秒级。
  • std::list/List
    • pop_back(): 需要释放链表节点的内存(调用freedelete)。内存分配器本身通常包含锁,且操作耗时远高于简单的指针运算。
    • push_back(): 需要分配新的链表节点内存(调用mallocnew)。

结论:使用vector可以将临界区缩小到仅包含几条汇编指令,极大降低了多线程竞争导致“获取锁失败”的概率。

2. 避免频繁的内存分配开销

  • std::list: 每次将对象放回池中(recycle),都需要为链表节点本身分配一次内存;每次取出对象,都要释放节点内存。这违背了对象池“减少内存分配”的初衷。
  • std::vector:
    • 代码中setSize调用了_objs.reserve(size)
    • recycle中有判断if (_objs.size() >= _pool_size)
    • 这意味着vector的底层数组一旦分配,在运行过程中几乎永远不需要重新分配或扩容。它仅仅是在一段连续内存上移动尾部指针。

3. 缓存局部性 (Cache Locality)

  • std::vector内部存储的是C*指针数组,这块内存在物理上是连续的。CPU 缓存(L1/L2 Cache)对连续内存的访问非常友好。
  • std::list的节点分散在堆内存的各个角落,遍历或访问时容易产生 Cache Miss。

4. 语义匹配 (LIFO Stack)

  • 对象池的操作逻辑是:用完放回去,要用取出来。并不关心对象的顺序。
  • 代码中使用的是back()(取栈顶) 和pop_back()(出栈) 以及emplace_back()(入栈)。
  • 这实际上是一个栈 (Stack)结构。在 C++ 中,std::vector是实现栈结构性能最好的容器(比std::stack适配器更直接)。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/13 4:23:08

pymzML终极指南:Python质谱数据分析快速入门

pymzML终极指南&#xff1a;Python质谱数据分析快速入门 【免费下载链接】pymzML pymzML - an interface between Python and mzML Mass spectrometry Files 项目地址: https://gitcode.com/gh_mirrors/py/pymzML 在蛋白质组学和代谢组学研究中&#xff0c;质谱数据分析…

作者头像 李华
网站建设 2026/3/12 23:20:36

Qwen3-14B-AWQ:颠覆传统的大模型轻量化部署革命

技术迷局&#xff1a;当140亿参数遇上4-bit量化 【免费下载链接】Qwen3-14B-AWQ 项目地址: https://ai.gitcode.com/hf_mirrors/Qwen/Qwen3-14B-AWQ 2025年&#xff0c;大模型领域正面临一个关键转折点&#xff1a;企业渴求AI能力&#xff0c;却被算力成本压得喘不过气…

作者头像 李华
网站建设 2026/3/12 7:49:36

全域众链:从需求到落地,五大核心维度验证 AI + 实体的可行性

在 “AI 实体经济” 的赛道中&#xff0c;不少项目因脱离实际需求、缺乏落地能力沦为概念炒作&#xff0c;而全域众链凭借对市场痛点的精准把握、闭环化的模式设计、实用型的技术支撑&#xff0c;成为少数经得住实践检验的落地型生态。其可行性并非空谈&#xff0c;而是由市场…

作者头像 李华
网站建设 2026/3/9 21:16:41

45.限界上下文进阶(下)-微服务拆分6个原则-避免拆太细或拆不开附拆分决策树

45 限界上下文进阶(下):微服务拆分的 6 个原则(避免 “拆太细” 或 “拆不开”) 你好,欢迎来到第 45 讲。 在上一讲,我们确立了微服务拆分的“第一性原理”:以限界上下文为边界。这个原则,为我们从“战略”上,指明了拆分的方向。 但是,在从战略走向战术的落地过程…

作者头像 李华
网站建设 2026/3/10 15:52:43

bRPC深度架构剖析:从核心机制到百万级实战优化

bRPC深度架构剖析&#xff1a;从核心机制到百万级实战优化 【免费下载链接】brpc 项目地址: https://gitcode.com/gh_mirrors/br/brpc bRPC框架作为百度开源的高性能RPC解决方案&#xff0c;在分布式通信领域展现出了卓越的性能表现。本文将从技术架构深度剖析、核心机…

作者头像 李华
网站建设 2026/3/8 17:40:18

本地部署文件共享软件 Jirafeau 并实现外网访问

Jirafeau 是一款允许一键文件共享的开源软件&#xff0c;上传文件方式简单&#xff0c;为其提供一个唯一的链接。能够发送任何大小的文件&#xff0c;在浏览器预览并提供密码保护。本文将详细的介绍如何利用 Docker 在本地部署 Jirafeau 并结合路由侠实现外网访问本地部署的 Ji…

作者头像 李华