【C++项目】从零实现高并发内存池(一):核心原理与设计思路

一:什么是内存池?

1. 池化技术

  • 定义 :程序先向系统申请过量资源,自己管理备用,避免频繁申请系统资源的开销。
  • 核心思想:提前申请 → 自行管理 → 快速复用,提升运行效率。
  • 常见应用:内存池、连接池、线程池、对象池等。
  • 线程池例子
    • 预先启动若干线程,让它们休眠;
    • 收到客户端请求时,唤醒池中一个线程处理;
    • 处理完后线程回到休眠状态,等待下次复用。

2. 内存池

  • 定义 :程序预先向操作系统申请一块足够大的内存 ,之后:
    • 申请内存:不从系统直接申请,而是从内存池获取;
    • 释放内存:不直接还给系统,而是归还到内存池;
    • 真正释放:程序退出或特定时机,才将内存还给系统。

3. 内存池主要解决的问题

  1. 效率问题
    • 避免频繁调用 malloc/free(系统调用开销大);
    • 内存池内部分配/回收速度更快,提升程序性能。
  2. 内存碎片问题
    • 频繁分配/释放不同大小内存,会产生大量无法利用的小内存块(内存碎片);
    • 内存池通过合理组织内存块,减少碎片,提高内存利用率。

以下这种是外碎片的例子,还回来的时候不连续所以造成空间碎片化

还有内碎片,我们在后面会提到

维度 内碎片 外碎片
位置 已分配内存块内部 已分配内存块之间的空闲区
本质 分配过剩,"用不完" 空闲分散,"凑不齐"
可见性 对分配者可见(占用了但没用) 对分配者不可见(整体空闲但用不了)
影响 单个内存块利用率低 无法满足大块内存分配请求
典型解决 精细化大小类、减少对齐填充 页合并、伙伴系统、内存池

4. malloc 的本质

  • C/C++ 动态申请内存的接口是 malloc,但它并非直接从堆获取内存
  • malloc 本身就是一个内存池实现
    • 向操作系统"批发"大块内存;
    • 再将这块内存"零售"给程序使用;
    • 当内存售罄或程序有大量需求时,才向操作系统再次"进货"(系统调用)。
  • malloc 有多种实现方案,不同编译器/平台使用不同版本:
    • Windows(VS系列):微软自研的 malloc 实现。
    • Linux(GCC) :glibc 中的 ptmalloc(最常见的开源实现)。

二:先设计一个定长的内存池学习一下

我们知道申请内存的使用的是malloc,malloc是通用的,但是任何场景都可以就意味着任何场景都不会有一个很高的性能,现在我们先设计一个定长的内存池去熟悉一下简单内存池是怎么控制的,以及会作为我们后面的内存池的一个组件。

主要实现了一个简单的定长内存池 ObjectPool,旨在通过复用内存块来减少频繁 new/delete 带来的系统开销

1. 核心模块解析

系统级内存分配 SystemAlloc

  • 功能:封装了操作系统层面的内存申请接口。

  • 实现 :在 Windows 下使用 VirtualAlloc 按页申请空间(kpage<<13kpage * 8KB)。

    cpp 复制代码
    inline static void* SystemAlloc(size_t kpage)
    {
        void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
        if (ptr == nullptr)
            throw std::bad_alloc();
        return ptr;
    }

内存池类 ObjectPool

这是一个模板类,用于管理特定类型 T 的对象内存。

  • 成员变量
    • _memory:指向当前正在使用的大块内存块的指针。
    • _remainBytes:记录当前大块内存剩余可用字节数 。
    • _freelist:自由链表头指针,指向已归还且可复用的对象内存。
  • 核心方法 New
    1. 优先复用 :检查 _freelist 是否为空。若不空,从链表头部取出一个节点返回。
    2. 内存不足时扩容 :若 _remainBtyes 不足,则通过向堆申请新的 128KB 内存块。
    3. 切割内存 :计算对象大小(考虑指针大小对齐),移动 _memory 指针,减少 _remainBtyes
    4. 构造对象 :使用 new (obj) T 在指定内存地址调用构造函数。
  • 核心方法 Delete
    1. 析构对象 :显式调用析构函数 obj->~T()
    2. 归还内存 :将对象指针通过头插法插入 _freelist 链表,供下次 New 复用。

测试结构 TreeNode 与测试函数 TestObjectPool

  • TreeNode:一个简单的二叉树节点结构,用于作为内存池管理的对象类型。
  • TestObjectPool
    • 对比逻辑 :分别使用原生 new/deleteObjectPoolNew/Delete 进行多轮次(Rounds)、大数量(N)的申请释放操作。
    • 计时 :使用 clock() 记录两种方式消耗的 CPU 时钟 ticks。
    • 预期结果 :通常情况下,ObjectPool 耗时会显著低于原生 new/delete,因为减少了系统调用和内存碎片。
    • 结果:成功达成提高性能效果,实现的内存池 相比直接 new 分配内存,性能提升了约 2.26 倍(111 / 49 ≈ 2.26),符合对象池技术的预期效果。

2. 关键总结

  • 自由链表(Free List) :通过 _freelist 将释放的对象内存串联起来。Delete 时将对象转为链表节点(利用对象内存本身存储 next 指针),New 时直接从链表头部摘取。
  • 内存块管理:不是一次性申请所有内存,而是当当前内存块(128KB)用完时,再申请新的块。
  • Placement New :使用 new (obj) T 语法,在已分配的原始内存上构造对象,分离了内存分配与对象构造。

三:高并发内存池整体框架设计

1.设计背景

现代开发多为多核多线程 环境,内存申请场景存在激烈锁竞争;常规malloc在高并发场景下性能不足,因此需设计高并发内存池(模仿tcmalloc),核心解决三大问题:

  1. 性能问题;
  2. 多线程环境锁竞争问题;
  3. 内存碎片问题。

二、整体架构(三层结构)

cmalloc采用thread cache + central cache + page cache三层架构,实现高并发下的高效内存管理。

1. thread cache(线程缓存)

这个的特点就是要的字节比较少的时候也会多给,

  • 属性每个线程独有,线程本地缓存,无锁竞争。
  • 适用场景 :分配小于256KB的小块内存。
  • 核心优势 :线程从自身thread cache申请内存时无需加锁,是高并发的核心支撑点。
  • 运行逻辑:thread cache按需从central cache获取内存对象;线程释放内存时,先放回自身thread cache(暂不立即归还central cache)。

2. central cache(中心缓存)

  • 属性所有线程共享的中心缓存层。
  • 核心作用
    • 统一管理内存对象的分配与回收,平衡各线程内存占用;
    • 避免单个线程长期占用过多内存,导致其他线程内存紧张,实现内存按需调度、均衡分配
  • 锁机制:存在锁竞争,但竞争程度低(原因:仅thread cache内存不足时才会向其申请,且采用桶机制细化锁粒度)。
  • 运行逻辑:thread cache内存耗尽时,向central cache申请批量内存对象;thread cache释放内存时,也归还给central cache统一管理。

3. page cache(页缓存)

  • 属性 :位于central cache上层的缓存层,以页为单位管理内存。
  • 核心作用
    • 为central cache提供大块内存支撑:central cache内存不足时,从page cache分配连续页,切割为固定大小小块内存供central cache使用;
    • 解决内存碎片 :当某个span的多个跨度页对应的对象全部回收后,page cache会回收这些满足条件的span,合并相邻的页,组成更大的连续页,缓解内存碎片。
  • 运行逻辑
    1. 向系统申请大块内存(页)时,优先从page cache获取;
    2. 回收central cache的内存时,先归还给page cache,再由page cache判断是否合并页、归还系统。

三、核心设计逻辑总结

|---------------|----------------|-----------------|----------------|
| 层级 | 核心特性 | 解决问题 | 关键机制 |
| thread cache | 线程独享、无锁、小内存分配 | 高并发下的锁竞争、性能瓶颈 | 线程本地缓存,批量申请/释放 |
| central cache | 全局共享、锁竞争弱、均衡调度 | 多线程内存分配不均、锁竞争激烈 | 桶机制+按需分配,减少锁粒度 |
| page cache | 页级管理、合并相邻页 | 内存碎片、大块内存分配 | 页级缓存+内存合并,缓解碎片 |

四、核心价值

通过三层分级缓存,将内存分配的锁竞争从"全局"下沉到"线程本地",大幅提升多线程并发分配效率;同时通过内存合并机制,有效降低内存碎片,兼顾性能与内存利用率。

相关推荐
雅欣鱼子酱1 小时前
Type-C供电PD协议取电Sink芯片ECP5702,可二端头分开供电调整亮度,适用于LED灯带户外防水超亮灯条方案
c语言·开发语言
浑水摸鱼仙君1 小时前
SpringSecurity和Flux同时使用报未认证问题
java·ai·flux·springsecurity·springai
一叶飘零_sweeeet2 小时前
Java 线程模型底层解密:从内核原理到生产级架构选型,全链路实战指南
java· java线程模型
似水明俊德2 小时前
07-C#
开发语言·c#
am心2 小时前
企业开发项目流程记录
java
浩子智控2 小时前
python程序打包的文件地址处理
开发语言·python·pyqt
Jackey_Song_Odd2 小时前
Part 1:Python语言核心 - 序列与容器
开发语言·windows·python
m0_662577972 小时前
Python迭代器(Iterator)揭秘:for循环背后的故事
jvm·数据库·python
Elnaij2 小时前
从C++开始的编程生活(20)——AVL树
开发语言·c++