C++高并发内存池实战:初步了解
本文深入探讨了C++高并发内存池的实现原理与技术细节。首先介绍了高并发系统的特点与内存池的核心概念,包括预分配内存、固定大小内存块等关键特性。重点分析了定长内存池的实现方法,详细展示了跨平台的系统调用封装(Malloc)、内存分配(New)与释放(Delete)接口的实现代码。文章最后勾勒出高并发内存池的三层架构:线程级缓存ThreadCache减少锁竞争,中心缓存CentralCache作为中介层,以及直接与操作系统交互的PageCache。该架构通过分层设计有效提升了多线程环境下的内存分配效率,为后续深

前言
针对前篇文章《源码剖析:STL中空间配置器(allocator)》中所描述的二级配置器小节中关于内存池的讲述,本人将专门对C++高并发内存池的理论以及实现细节进行针对性讲解。本篇文章将简单的对C++高并发内存池的理论以及结构分层进行描述,后续文章将通过分层的方式,对C++高并发内存池的理论以及细节进行讲解,还会配上实现源码方便读者理解。
什么是高并发?
高并发是指系统能够同时处理大量并行请求或操作的能力。在项目例程中往往会存在一个进程含有多个线程的情况,当多个线程同时向内存池申请空间分配时,高并发的能力变得尤为重要,一个稳定的,快速的处理并发的操作,使高并发内存池的关键
什么是内存池?
内存池指的是一种内存管理技术,特别是在需要频繁进行内存申请和释放的场景中,为了优化动态内存分配和释放的性能所实现。其核心思想是向系统预先分配一块内存,然后在这块内存中以更高效的方式管理和分配内存块
以下是内存池的几个关键特点:
1.预分配内存:在内存池初始化时,会预先从操作系统申请一块内存,用于内存分配
2.固定大小的内存块:通常包含多个固定大小的内存块,这些内存块可以被重复使用,减少了内存分配和释放的开销
3.减少内存碎片:由于内存池中的内存块大小固定,可以减少内存碎片的产生,提高内存使用效率
4.提高性能:减少系统调用和内存复制,显著提高内存分配和释放的速度
5.简化内存管理:不需要直接使用malloc()函数和free()函数等管理内存,只需与内存池交互
6.多线程支持:支持多线程环境(高并发),能够在没有锁或使用锁的情况下安全地进行内存分配和释放
7.灵活性:支持不同大小的内存块,以适应不同的内存分配需求
什么是池化技术?
池化技术指的是进程先向系统申请过量的资源,然后自行进行管理,以备不时之需。由于向系统申请空间等资源会产生上下文切换操作,导致系统开销较大,所以提前申请资源,当需要资源时则可以直接从"池"中获取,不需要时就将该资源重新放回"池"中即可,从而达到提高程序的运行效率的目的
什么是定长内存池?
定长内存池指的是针对固定大小内存块的申请和释放的内存池,所以在实现定长内存池时不需要考虑内存碎片等问题。接下来将对如何简单实现一个定长内存池进行讲解,并使用代码对其实现:
1.导入相关库:
1.<unistd.h>头文件:包含一组通用的Unix系统调用,这些调用操作与POSIX标准兼容
2.<windows.h>头文件:包含编写 Windows 应用程序时与操作系统进行交互的接口
3.<sys/mman.h>头文件:包含内存映射的函数和常量,常用于实现文件I/O操作
#pragma once //防止重复包含该文件#include <iostream>using std::cout;using std::endl;#ifdef WIN32 //在Windows系统上运行 #include <windows.h>#else //Linux或Unix系统上运行 #include <sys/mman.h> #include <unistd.h>#endif2.实现定长内存池成员:
char* _memory = nullptr; //指向向系统申请的内存地址size_t _remainBytes = 0; //记录内存池剩余字节数void* _freeList = nullptr; //链表,用于管理内存块
图1.定长内存池成员设计讲解图
3.实现内存分配接口:
1.MEM_COMMIT:用于将内存区域标记为已提交状态。意味着操作系统将分配物理存储器(如RAM)给指定的内存区域,使其可被进程访问和修改
2.MEM_RESERVE:用于保留或预订内存地址空间,但并不立即分配物理存储。与MEM_COMMIT一起使用时,可以先预订地址空间,再逐步提交为可访问的内存
3.PAGE_READWRITE:用于定义内存的访问权限,表示内存页面可以被读取和写入
4.PROT_READ:用于在Unix或Linux系统中指定映射的内存区域具有读权限
5.PROT_WRITE:用于在Unix或Linux系统中指定映射的内存区域具有写权限
6.MAP_PRIVATE:用于创建一个私有的内存映射。对私有映射的修改不会反映到文件上,也不会被其他映射该文件的进程看到
7.MAP_ANONYMOUS:用于创建一个匿名映射,意味着映射不与任何文件关联
8.MAP_FAILEDD:用于标记mmap()函数调用失败
inline static void* Malloc(size_t RAM_Size){ void* ptr;#ifdef _WIN32 ptr = VirtualAlloc(0, RAM_Size << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); //Windows下使用VirtualAlloc()进行内存分配#else ptr = mmap(NULL, RAM_Size << 13, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); //Linux下使用mmap()进行内存分配#endif if (ptr == MAP_FAILED){ //检查内存是否分配失败 throw std::bad_alloc(); //抛出bad_alloc()异常 } return ptr;}4.定长内存池对象应实现的函数:
1.New()函数:
template<class T>T* New() //实现定长内存池分配空间函数{ T* obj = nullptr; //定义obj指针指向分配的内存地址 if (_freeList != nullptr){ //判断链表中是否存在空闲内存 obj = (T*)_freeList; //优先使用分配过的内存 _freeList = *(void**)_freeList; //强转为void**后解引用为void* //用于判断下一个空闲指针的位置,使链表指针指向下一个空闲地址 } else { size_t objSize = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*); //确保至少能存储一个指针大小 if (_remainBytes < objSize) //申请的内存空间不足 { _remainBytes = 128 * 1024; _memory = (char*)Malloc(_remainBytes >> 13); //将空闲地址中的13个内存分配给_memory } obj = (T*)_memory; _memory += objSize; _remainBytes -= objSize; } new(obj)T; //原地构造new(),调用对象构造函数在指定的地址上初始化 return obj;}2.Delete()函数:
template<class T>void Delete(T* obj){ obj->~T(); //调用析构函数清理对象 *(void**)obj = _freeList; //使用头插法,将归还的内存插入链表 _freeList = obj;}5.整体实现:
#pragma once#include <iostream>using std::cout;using std::endl;#ifdef _WIN32 #include<windows.h>#else #include<sys/mman.h> #include<unistd.h>#endifinline static void* Malloc(size_t RAM_Size){ void* ptr;#ifdef _WIN32 ptr = VirtualAlloc(0, RAM_Size << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);#else ptr = mmap(NULL, RAM_Size << 13, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);#endif if (ptr == nullptr){ throw std::bad_alloc(); } return ptr;}template<class T>class ObjectPool{public: T* New() { T* obj = nullptr; if (_freeList){ void* next = *((void**)_freeList); obj = (T*)_freeList; _freeList = next; } else { if (_remainBytes < sizeof(T)) { _remainBytes = 128 * 1024; _memory = (char*)Malloc(_remainBytes >> 13); if (_memory == nullptr) { throw std::bad_alloc(); } } obj = (T*)_memory; size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T); _memory += objSize; _remainBytes -= objSize; } new(obj)T; return obj; } void Delete(T* obj){ obj->~T(); *(void**)obj = _freeList; _freeList = obj; }private: char* _memory = nullptr; size_t _remainBytes = 0; void* _freeList = nullptr;};高并发内存池的整体框架
此次只针对高并发内存池的整体架构进行简单描述,了解ThreadCache,CentralCache和PageCache三个分层的简单概念:
1.ThreadCache:是与特定线程直接关联的缓存,为每个线程提供了一个私有的内存分配空间。每个线程可以快速地从自己的ThreadCache中分配和释放内存,而无需进入全局锁或与其他线程争用,从而减少了锁竞争和上下文切换,提高了内存分配的效率
2.CentralCache:作为一个全局或中心缓存存在,是多个ThreadCache之间的中介。当某个线程的ThreadCache耗尽时,它可以从CentralCache中获取内存块(通常是较大的内存块),然后再将这些内存块细分为适合ThreadCache使用的大小
3.PageCache:是直接与操作系统的内存页面交互的缓存级别。负责管理大的内存区域(页面),并将这些页面分配给CentralCache或直接映射到ThreadCache

图2.C++高并发内存池框架图