【高并发内存池——项目】定长内存池——开胃小菜 - 详解

提示:高并发内存池完整项目代码,在主页专栏项目中

先设计一个定长的内存池

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

#include
#include
#include
using std::cout;
using std::endl;
#ifdef _WIN32
#include
#else
//
#endif
// 定长内存池
//template
//class ObjectPool
//{};
// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
void* ptr = VirtualAlloc(0, kpage
class ObjectPool
{
public:
T* New()
{
T* obj = nullptr;
// 优先把还回来内存块对象,再次重复利用
if (_freeList)
{
void* next = *((void**)_freeList);
obj = (T*)_freeList;
_freeList = next;
}
else
{
// 剩余内存不够一个对象大小时,则重新开大块空间
if (_remainBytes > 13);
if (_memory == nullptr)
{
throw std::bad_alloc();
}
}
obj = (T*)_memory;
size_t objSize = sizeof(T) ~T();
// 头插
*(void**)obj = _freeList;
_freeList = obj;
}
private:
char* _memory = nullptr; // 指向大块内存的指针
size_t _remainBytes = 0; // 大块内存在切分过程中剩余字节数
void* _freeList = nullptr; // 还回来过程中链接的自由链表的头指针
};

一、为什么需要定长内存池?

在C++开发中,频繁的内存分配和释放是性能瓶颈的主要来源之一。让我们先看一个现实中的比喻:

传统内存分配的痛点

想象每次需要办公桌时都现买:

  • ⏰ 时间开销大:每次都要去家具市场

  •  成本高昂:中间商赚差价(内存碎片)

  •  效率低下:无法批量优化

内存池的解决方案

像大型办公室统一采购:

  •  批量获取:一次性申请大块内存

  • ⚡ 快速分配:直接从池中分配,无需系统调用

  •  重复利用:释放的内存放回池中复用

  •  减少碎片:固定大小分配,无外部碎片

二、定长内存池核心设计思想

1. 整体架构

三大核心组件:

  • ️ 大块内存:从系统申请的内存块

  •  空闲链表:管理已释放可重用的内存块

  •  分配策略:决定如何分配新内存

2. 类定义解析

template
class ObjectPool
{
private:
char* _memory = nullptr;     // 指向大块内存的指针
size_t _remainBytes = 0;     // 剩余可用字节数
void* _freeList = nullptr;   // 空闲链表头指针
};

三、关键技术实现深度解析

1. 内存申请策略

// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
// Linux下使用brk或mmap等系统调用
#endif
if (ptr == nullptr)
throw std::bad_alloc();
return ptr;
}

设计要点:

  • ️ 跨平台支持:Windows使用VirtualAlloc,Linux使用mmap

  •  按页分配:以页面为单位(通常4KB),减少系统调用次数

  •  异常安全:分配失败抛出bad_alloc异常

2. 内存分配(New方法)

T* New()
{
T* obj = nullptr;
// 优先复用空闲链表中的内存块
if (_freeList)
{
void* next = *((void**)_freeList);  // 获取下一个空闲块
obj = (T*)_freeList;                // 当前块作为分配对象
_freeList = next;                   // 更新空闲链表头
}
else
{
// 剩余内存不足时申请新的大块内存
if (_remainBytes > 13); // 计算页数
if (_memory == nullptr)
{
throw std::bad_alloc();
}
}
// 从大块内存中切分
size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
obj = (T*)_memory;
_memory += objSize;
_remainBytes -= objSize;
}
// 定位new调用构造函数
new(obj)T;
return obj;
}

关键技术点:

  1. 空闲链表优先:先尝试从空闲链表获取已释放的内存

  2. 内存对齐:确保每个对象至少sizeof(void*)大小,便于链表操作

  3. 批量申请:一次性申请128KB内存,减少系统调用

  4. 定位new:在指定内存地址调用构造函数

3. 内存释放(Delete方法)

void Delete(T* obj)
{
// 调用析构函数清理对象
obj->~T();
// 头插法将内存块加入空闲链表
*(void**)obj = _freeList;
_freeList = obj;
}

设计精髓:

  •  资源清理:显式调用析构函数

  •  链表管理:使用头插法将释放的内存加入空闲链表

  • ⚡ 高效复用:释放的内存立即可用于下次分配

四、空闲链表技术的巧妙运用

1. 链表存储原理

关键技巧: 利用内存块本身存储链表指针

// 释放时:将当前内存块的前4/8字节存储下一个节点的地址
*(void**)obj = _freeList;  // 将_freeList值存入obj指向的内存
_freeList = obj;           // 更新链表头
// 分配时:从链表头取出节点,并更新头指针
void* next = *((void**)_freeList);  // 读取下一个节点地址
obj = (T*)_freeList;                // 当前节点作为分配对象
_freeList = next;                   // 更新链表头

2. 内存对齐的重要性

size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);

为什么需要对齐?

  •  最小大小:确保每个内存块至少能存储一个指针(4或8字节)

  •  地址对齐:保证指针操作的正确性

  • ⚡ 访问效率:对齐的内存访问速度更快

五、性能优势分析

与传统malloc对比

特性传统malloc定长内存池
分配速度较慢(系统调用)极快(直接操作内存)
内存碎片可能产生外部碎片无外部碎片
系统调用每次分配都可能调用批量申请,极少调用
线程安全需要加锁可设计为线程本地
适用场景通用分配固定大小对象

posted @ 2025-09-16 20:45  wzzkaifa  阅读(14)  评论(0)    收藏  举报