跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 高并发内存池:ObjectPool 构造与实现

定长内存池作为对象池的基础组件,适用于高成本初始化、有限资源及频繁使用场景。通过封装 VirtualAlloc 或 mmap 系统调用按页申请内存,利用链表管理空闲节点实现对象分配与回收。相比 malloc,自定义内存池在频繁创建销毁对象时能显著提升性能,减少碎片并优化内存访问效率。

KernelLab发布于 2026/3/15更新于 2026/6/621 浏览
C++ 高并发内存池:ObjectPool 构造与实现

定长内存池通常被用作对象池(ObjectPool),整个内存池只用来给一种数据结构用。

对象池适用于以下场景:

  1. 高成本初始化:如 StringBuilder 或大型缓冲区。
  2. 有限资源:如数据库连接或线程。
  3. 频繁使用:如在高并发环境中重复使用的对象

1. 定长内存池的介绍

作为程序员(C/C++)我们知道申请内存使用的是 malloc,malloc 其实就是一个通用的大众货,什么场景下都可以用,但是什么场景下都可以用就意味着什么场景下都不会有很高的性能。下面我们就先来设计一个定长内存池做个开胃菜,当然这个定长内存池在我们后面的高并发内存池中也是有价值的,所以学习它目的有两层,先熟悉一下简单内存池是如何控制的,第二它会作为我们后面内存池的一个基础组件。

2. 定长内存池的构建

2.1 内存申请的系统调用

由于 malloc 的局限性,我们在内存申请方面使用系统调用,提高效率。

并且在操作系统中,系统通常以页为单位进行数据的访问和保存,通常一页是 4kb/8kb(我们选择 8kb),所以我们向系统申请内存时,最好是以页为单位申请。

关于内存申请的系统调用:

Windows: VirtualAlloc Linux: brk() 和 mmap()

我们再把系统调用封装一层(函数内),利用条件编译,可以实现跨平台使用。

#define PAGE_SHIFT 13 // (kpage << PAGE_SHIFT) 表示一页的字节大小 8kb = 8 * 1024,即 2*13
#ifdef _WIN32
#include <Windows.h>
#elif __linux__
#include <unistd.h>
#endif

// 直接去堆上按页申请空间
// 封装系统调用
inline static void* SystemAlloc(size_t kpage) // kpage:页数
{
#ifdef _WIN32
    void* ptr = VirtualAlloc(0, kpage << PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
    void* ptr = mmap(NULL, kpage << PAGE_SHIFT, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (ptr == MAP_FAILED) ptr = nullptr;
#endif
    if (ptr == nullptr) throw std::bad_alloc();
    return ptr;
}

2.2 ObjectPool 的总体框架构建

ObjectPool 是只给一种数据结构用的内存池,因此我们可以用模板,来指定所用的数据结构。

当我们向系统申请内存后,需要一个指针指向其内存的首地址,即_memory。 其中进程始终先拿_memory 所指向一块资源。

当向 ObjectPool 的头部拿走了一块资源后,需要把_memory += sizeof(T)。 直到_memory 到了内存池底部,有可能剩余的空间不够一个 class T 使用,而我们需要判断这个状况,因此我们再引入一个_left_size 的变量,表示内存池剩余的空间。 如果_left_size < sizeof(T),那么需要重新开辟空间,_memory 指向新的内存池,_left_size 也得更新。

向内存池申请空间我们讲完了,那么如果释放资源如何处理?难道我们再让_memory += sizeof(T)吗?但是我们不知道收回的那块资源在哪个位置,不可胡乱加。

因此这里我们把收回的每一块资源用链表串起来(逻辑角度),用_free_list 表示最前面的节点的地址。 这样如果又有进程要来申请资源时,我们可以优先看_free_list 中有没有空余的资源,如果有直接拿走即可。

那么问题又来了,如何把这几块资源用链表串在一起?以前我们用的结构体内存在 next 指针,但一块空间可不存在 next 指针的变量。 这里我们把每一块的前 4/8 个字节(一个指针的大小)放入下一个节点的地址,如果每一块不够一个指针的大小,则在申请资源的时候强制给一个指针的大小。

为了方便,我们专门引入一个函数,来查找链表中一个节点的下一个节点,即访问前 4/8 个字节。

static void*& Next_Obj(void* obj) { return *((void**)obj); } // 自动访问一个指针大小的空间,32 位下为 4 个字节,64 位下为 8 个字节

这样 ObjectPool 的大框架就出来了。

template<class T> class ObjectPool {
public:
    T* New() {}
    void Delete(T* obj) {}
private:
    char* _memory = nullptr;      // 剩余空间的首地址
    size_t _left_size = 0;        // 剩余空间的大小
    void* _free_list = nullptr;   // 回收链表的首节点地址
};

2.3 New 函数实现

回顾之前的申请资源的思路:

  1. 先看_free_list 有没有空余资源,如果有,优先从这拿。
  2. 如果_free_list 为空(没有空余资源),则拿走有_memory 指向的一块资源。

其中从_memory 拿又有两种情况:

  1. 剩余空间大于对象的大小,直接拿。
  2. 剩余空间不够,需要重新开辟。

代码实现:

T* New() {
    T* obj = nullptr;
    if (_free_list) // 先看_free_list 为不为空
    {
        void* next = *((void**)_free_list);
        obj = (T*)_free_list;
        _free_list = next;
    }
    else
    {
        if (_left_size < sizeof(T)) // 剩余空间不够
        {
            _memory = (char*)SystemAlloc(DEFAULT_KPAGE_NUM); // 重新开辟
            _left_size = DEFAULT_KPAGE_SIZE;
            if (_memory == nullptr)
            {
                throw std::bad_alloc();
            }
        }
        obj = (T*)_memory;
        size_t obj_size = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T); // 如果对象的大小不够一个指针的大小,则给的时候强制给一个指针的大小
        _memory += obj_size;
        _left_size -= obj_size;
    }
    new(obj) T;
    return obj;
}

2.4 Delete 函数的实现

当回收资源时,串在_free_list 的链表中。

void Delete(T* obj) {
    obj->~T();
    *((void**)obj) = _free_list;
    _free_list = obj;
}

2.5 测试性能

我们可以跟 malloc 对比一下,哪个更快。

测试代码:

struct TreeNode {
    int _val;
    TreeNode* _left;
    TreeNode* _right;
    TreeNode() :_val(0), _left(nullptr), _right(nullptr) {}
};

void TestObjectPool() {
    // 申请释放的轮次
    const size_t Rounds = 5;
    // 每轮申请释放多少次
    const size_t N = 100000;
    std::vector<TreeNode*> v1;
    v1.reserve(N);
    size_t begin1 = clock();
    for (size_t j = 0; j < Rounds; ++j)
    {
        for (int i = 0; i < N; ++i)
        {
            v1.push_back(new TreeNode);
        }
        for (int i = 0; i < N; ++i)
        {
            delete v1[i];
        }
        v1.clear();
    }
    size_t end1 = clock();

    std::vector<TreeNode*> v2;
    v2.reserve(N);
    ObjectPool<TreeNode> TNPool;
    size_t begin2 = clock();
    for (size_t j = 0; j < Rounds; ++j)
    {
        for (int i = 0; i < N; ++i)
        {
            v2.push_back(TNPool.New());
        }
        for (int i = 0; i < N; ++i)
        {
            TNPool.Delete(v2[i]);
        }
        v2.clear();
    }
    size_t end2 = clock();
    cout << "new cost time:" << end1 - begin1 << endl;
    cout << "object pool cost time:" << end2 - begin2 << endl;
}

显然我们的 objectpool 更快。

目录

  1. 1. 定长内存池的介绍
  2. 2. 定长内存池的构建
  3. 2.1 内存申请的系统调用
  4. 2.2 ObjectPool 的总体框架构建
  5. 2.3 New 函数实现
  6. 2.4 Delete 函数的实现
  7. 2.5 测试性能
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python 爬虫技术实战指南:从入门到分布式采集
  • MyBatis 缓存机制详解:一级与二级缓存
  • Linux 库制作与原理:从生成使用到 ELF 文件与链接解析
  • 使用 NVM 安装 Node.js 22 并配置国内镜像加速
  • Java Object 类详解:继承体系与常用方法解析
  • Linux 命令行核心指令与权限控制指南
  • 基于 Python 和 Selenium 的浏览器操作录制器实现
  • C++ ODB ORM 框架入门与实战
  • 4G Cat.1 模组赋能 AI 教育机器人:政策与技术的双重驱动
  • OpenClaw 30+ 真实使用案例开源,参考 AI 助理落地方案
  • C++ 运算符重载:自定义类型的运算扩展
  • MAVROS 安装配置、基础概念与 ROS C++仿真案例
  • FPGA 摄像头采集处理显示指南:OV5640 到 HDMI 实时显示
  • C++ 算法刷题:气球排列、迷宫搜索与主持人调度
  • Vivado 许可证获取与配置实战指南
  • 自然语言处理:大模型理论与实践
  • 基于 FastAPI 的 Web 上位机系统设计与实现
  • AI 绘画敏感内容控制:提示词策略与安全实践
  • Python 基础入门:变量概念与数据类型详解
  • 自然语言处理技术与应用实践

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online