当前位置: 首页 > news >正文

LinuxC++项目开发日志——高并发内存池(6-内存回收机制)

文章目录

  • 回收机制流程
    • 一、回收机制的核心分层设计
    • 二、详细回收流程(从用户释放到系统回收)
      • 1. 第一步:用户释放内存到 ThreadCache
      • 2. 第二步:ThreadCache 批量回收至 CentralCache
      • 3. 第三步:CentralCache 回收至 PageCache(合并内存页)
      • 4. 第四步:PageCache 归还给操作系统(可选)
    • 三、回收机制的关键优化点
  • 整体代码
    • common.h
    • freelist类
    • lockguard类(未用)
    • Log类
    • linux mutex类封装
    • objectpool类(预备)
    • sizecalss类
    • span类
    • spanlist类
    • thread cache类
    • central cache类
    • page cache类
    • test测试函数

回收机制流程

一、回收机制的核心分层设计

tcmalloc 风格的内存池采用三级缓存结构,回收流程也围绕这三级结构展开,每层负责不同粒度的内存管理:

ThreadCache(线程缓存):每个线程私有,无锁访问,管理小内存块(通常 ≤ 256KB);
CentralCache(中心缓存):全局共享,按内存块大小分桶加锁,管理中等大小内存;
PageCache(页缓存):全局共享,管理大内存块(通常 ≥ 1 页,4KB),与系统内核交互(brk/mmap)。

回收方向:用户释放内存 → ThreadCache → CentralCache → PageCache → 归还给操作系统(选择

二、详细回收流程(从用户释放到系统回收)

1. 第一步:用户释放内存到 ThreadCache

用户调用 free 或自定义 Deallocate 接口释放内存时,首先进入线程私有缓存 ThreadCache:

判断内存块大小:根据内存块大小(通过头部元信息或哈希计算),找到对应的 FreeList(每个 FreeList 管理固定大小的内存块);

无锁回收:直接将内存块插入 FreeList 头部(单链表头插,O (1) 操作);

检查阈值:若 FreeList 中的内存块数量超过预设阈值(如 max_size),触发 批量回收 到 CentralCache(避免 ThreadCache 囤积过多内存)。

void Deallocate(void* ptr, size_t size){// 1. 合法性检查:排除无效大小(≤0或超MAX_BYTES)、空指针(避免非法访问)if(size <= 0 || size > MAX_BYTES || ptr == nullptr){Log(WARNING) << "size invalid or ptr is null!";  // 打印警告日志(提示非法参数)return;  // 直接返回(释放失败,无操作)}// (可选)确保大小是对齐后的标准大小(防止因外部传入错误原始大小导致的索引错误)size = SizeClass::RoundUp(size);// 2. 计算哈希索引:根据原始大小找到对应的FreeList位置(无需对齐,因分配时已对齐,原始大小可反推索引)int index = SizeClass::Index(size);// 3. 将内存块归还给本地自由链表:线程本地操作,无锁,直接将块插入链表头部_freelists[index].Push(ptr);// 4. 缓存囤积检查:当FreeList长度超过其最大限制时,将多余内存块归还给CentralCache// 目的:避免单个线程囤积过多内存,导致其他线程内存不足(平衡线程间内存分配)if(_freelists[index].Size() >= _freelists[index].Maxsize()){
#if _DEBUG == 1//Log(LogModule::DEBUG) << "回收机制开启!" << "size/maxsize: " //<< _freelists[index].Size() << "/" << _freelists[index].Maxsize();
#endifListTooLong(_freelists[index], size);}}
void ListTooLong(FreeList& list, size_t size)
{// 实现逻辑:从FreeList中取出"超过最大限制的部分"(此处简化为取出最大限制数量的块),归还给CentralCachevoid* start = nullptr;  // 待归还的内存块链表头void* end = nullptr;    // 待归还的内存块链表尾// 从FreeList中批量弹出"最大限制数量"的块(具体弹出数量可根据策略调整,如弹出一半)list.PopRange(start, end, list.Maxsize());// 调用CentralCache的接口,将弹出的块链表归还给中心缓存(CentralCache会进一步合并或管理)CentralCache::GetInstance()->RealseListToSpans(start, size);
}

2. 第二步:ThreadCache 批量回收至 CentralCache

当 ThreadCache 的某个 FreeList 内存块过多时,会将超出阈值的部分批量归还给 CentralCache:

加桶锁:CentralCache 按内存块大小分桶,仅锁定对应桶的 SpanList(减少锁竞争);

批量转移:从 ThreadCache 的 FreeList 中弹出批量内存块(如一半或阈值以上部分),链表形式转移给 CentralCache;

更新 Span 计数:CentralCache 中管理内存块的 Span(页跨度)会记录 “已使用块数”(_use_count–),跟踪内存块使用状态。

void RealseListToSpans(void* start, size_t alignsize){// 1. 获取对应的SpanList(根据内存块大小的索引),并加桶锁(确保线程安全)SpanList& spanlist = _spanlist[SizeClass::Index(alignsize)];std::lock_guard<std::mutex> lock(spanlist._mutex);// 2. 遍历内存块链表,逐个归还给对应的Spanwhile (start) {void* next = NextObj(start);  // 先记录下一个块(避免归还后丢失链表)// 2.1 查找当前块所属的Span(通过PageCache的地址映射接口)PageCache* pc = PageCache::GetInstance();Span* span = pc->AddrToSpan(start);if(span == nullptr)  // 地址映射失败(异常情况){Log(LogModule::ERROR) << "span find fail!";  // 错误日志(排查内存块归属问题)return;}// 2.2 将当前块归还给Span的自由链表(头插法)span->_usecount--;               // 已使用块计数-1(表示归还一个块)NextObj(start) = span->_freelist; // 当前块的next指针指向Span原自由链表头span->_freelist = start;          // 更新Span的自由链表头为当前块// 2.3 检查Span是否已完全空闲(所有块都被归还)if(span->_usecount == 0){// 从CentralCache的SpanList中移除该Span(不再管理,准备归还PageCache)spanlist.erase(span);// 重置Span状态(除页ID和页数外,清空其他字段如自由链表、使用计数等)span->ReSet();// 将完全空闲的Span归还给PageCache(PageCache会进行合并,减少碎片)pc->lock();  // 锁定PageCache(跨线程操作页缓存)pc->RealseSpanToPageCache(span);pc->unlock();  // 解锁PageCache}start = next;  // 处理下一个内存块}}

3. 第三步:CentralCache 回收至 PageCache(合并内存页)

CentralCache 接收 ThreadCache 归还的内存块后,若某个 Span 的所有内存块都被归还(_use_count == 0),则将整个 Span 归还给 PageCache:

解锁 Span:将 Span 从 CentralCache 的 SpanList 中移除,重置状态(如清空自由链表);

加全局锁:PageCache 采用全局锁(因大内存操作频率低,锁竞争小);

合并相邻 Span:PageCache 接收 Span 后,检查其前后是否有连续的空闲 Span(通过页 ID 连续性判断),合并为更大的 Span(减少内存碎片);

判断是否归还给系统:若合并后的 Span 页数超过阈值(如 1MB),则调用 munmap 归还给操作系统(小 Span 保留在 PageCache 中供后续分配)。(可选操作)

void RealseSpanToPageCache(Span* span)
{if(span == nullptr){Log(LogModule::DEBUG) << "span invalid!";return;}MergePage(span); // 调用合并逻辑,将该Span与相邻空闲Span合并(减少内存碎片)
}
void MergePage(Span* span){// -------------------------- 第一步:向前合并(合并当前Span的前一个空闲Span)--------------------------while (true) {// 当前Span的起始页ID(用于找前一个页ID)PageID curid = span->_pageid;// 查找前一个页ID(curid-1)是否存在映射(即是否有相邻的前Span)auto it = _idtospanmap.find(curid - 1);// 前一个页ID无映射(无相邻前Span),终止向前合并if(it == _idtospanmap.end()){break;}// 找到前一个页ID对应的Span(prevspan)Span* prevspan = it->second;// 合并条件检查:前Span必须未被使用(_isUse=false),且合并后总页数不超过最大支持页数(NPAGES-1)if(prevspan->_isUse == true || prevspan->_n + span->_n > NPAGES - 1){break; // 不满足合并条件,终止向前合并}PopIdToMap(prevspan->_pageid, prevspan->_n);// 执行合并:将前Span的内存合并到当前Span中span->_pageid = prevspan->_pageid;  // 当前Span的起始页ID = 前Span的起始页ID(向前扩展)span->_n += prevspan->_n;           // 当前Span的页数 = 原页数 + 前Span的页数// 清理前Span:从其所属的SpanList桶中删除,再释放前Span对象bool b = _spanlist[prevspan->_n].erase(prevspan);if(!b){Log(LogModule::ERROR) << "prevspan erase fail!";}delete prevspan; // 释放前Span的堆内存(避免内存泄漏)}// -------------------------- 第二步:向后合并(合并当前Span的后一个空闲Span)--------------------------while (true) {// 当前Span的起始页ID(用于找后一个页ID:当前Span的结束页ID+1 = 起始页ID + 页数)PageID curid = span->_pageid;auto it = _idtospanmap.find(curid + span->_n);// 后一个页ID无映射(无相邻后Span),终止向后合并if(it == _idtospanmap.end()){break;}// 找到后一个页ID对应的Span(nextspan)Span* nextspan = it->second;// 合并条件检查:后Span必须未被使用,且合并后总页数不超过最大支持页数if(nextspan->_isUse == true || nextspan->_n + span->_n > NPAGES - 1){break; // 不满足合并条件,终止向后合并}PopIdToMap(nextspan->_pageid, nextspan->_n);// 执行合并:将后Span的内存合并到当前Span中(只需更新页数,起始页ID不变)span->_n += nextspan->_n;           // 当前Span的页数 = 原页数 + 后Span的页数// 清理后Span:从其所属的SpanList桶中删除,再释放后Span对象bool b = _spanlist[nextspan->_n].erase(nextspan);if(!b){Log(LogModule::ERROR) << "nextspan erase fail!";}delete nextspan; // 释放后Span的堆内存}// -------------------------- 第三步:合并后的Span回桶 + 维护映射 --------------------------// 1. 将合并后的大Span放入对应页数的SpanList桶中(桶索引=合并后的页数)_spanlist[span->_n].PushFront(span);// 2. 删除合并后Span的全页映射(原映射可能包含合并前的页ID,需清理)//PopIdToMap(span->_pageid, span->_n);// 3. 建立合并后Span的首尾页映射(空闲Span只需维护首尾,减少哈希表开销,方便后续合并)PushIdToMap(span->_pageid, span->_n, span);          // 起始页ID映射//PushIdToMap(span->_pageid + span->_n - 1, 1, span); // 结束页ID映射}

4. 第四步:PageCache 归还给操作系统(可选)

PageCache 中合并后的大 Span(如超过 1024 页)会被归还给系统,减少内存占用:

调用系统接口:通过 munmap 释放连续的内存页(brk 分配的小内存通常不主动归还给系统,由内核管理);
保留小 Span:小 Span(如 ≤ 128 页)会留在 PageCache 的空闲链表中,供后续快速分配(避免频繁系统调用开销)。

三、回收机制的关键优化点

延迟回收(Lazy Release)

内存不会立即归还给操作系统,而是先在各级缓存中保留一段时间,供后续分配复用(系统调用 munmap 开销高,尽量避免)。

分级锁策略

  • ThreadCache 无锁(线程私有);

  • CentralCache 按桶加锁(不同大小的内存块操作并行);

  • PageCache 全局锁(但大内存分配 / 回收频率低,冲突少)。

内存合并(Coalescing)

PageCache 阶段合并相邻空闲 Span,解决外部碎片问题(小内存块分散导致无法分配大内存)。

阈值控制
每层缓存通过阈值(如 ThreadCache 的 max_size、PageCache 的归还阈值)平衡内存利用率和回收效率,避免缓存过多或频繁系统调用。

整体代码

common.h

#pragma once  // 防止头文件被重复包含#include <cstddef>
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include "Log.hpp"          // 日志模块头文件
#include "Lockguard.hpp"    // 锁保护模块头文件
#ifdef __linux__#include <sys/mman.h>
#else#include<windows.h>
#endif// 1. 先定义“开关宏”:1=启用调试,0=禁用调试
#define DEBUG_MODE 0  // 这里是总开关,修改这个值即可控制全局调试模式// 2. 根据开关宏控制 _DEBUG 的定义
#if DEBUG_MODE == 1
#define _DEBUG 1  // 调试模式:启用所有调试相关逻辑
#else
#define _DEBUG 0  // 发布模式:禁用调试逻辑(显式定义为0,避免未定义)
#endifusing std::cout;
using std::endl;
using namespace LogModule;      // 使用日志模块的命名空间
using namespace LockGuardModule; // 使用锁保护模块的命名空间// 自由链表的总数量:208个桶,对应不同大小的内存块
static constexpr size_t NFREELISTS = 208;// 线程缓存能处理的最大内存大小:256KB
// 超过这个大小的内存申请直接走中央缓存或页堆
static constexpr size_t MAX_BYTES = 256 * 1024;//页缓存的数组数量,下标数字表示存贮的多少页的span
static constexpr size_t NPAGES = 129;//页大小
static constexpr size_t PAGESIZE = 1 << 12;
//页偏移
static constexpr size_t PAGE_SHIFT = 12;// 如果是Windows 32位或64位系统(_WIN32是Windows平台的预定义宏)
#ifdef _WIN32// 在Windows系统上,将PageID定义为size_t类型// size_t在Windows中通常与指针位数一致(32位系统为4字节,64位系统为8字节)typedef size_t PageID;
#else// 对于非Windows系统(如Linux、macOS等类Unix系统)// 将PageID定义为unsigned long long类型(固定8字节,可表示更大范围的页编号)typedef unsigned long long PageID;
#endif/*** @brief 获取内存块中的下一个对象指针* @param ptr 当前内存块的指针引用* @return void*& 下一个对象的指针引用* * 这个函数用于实现自由链表的连接功能。它将内存块的前8字节(在64位系统)* 用作存储下一个内存块的地址,实现链式结构。* * 工作原理:* 1. 将ptr强制转换为void**(指向指针的指针)* 2. 解引用得到void*&(指针的引用)* 3. 这样就可以直接修改下一个节点的地址*/
inline void*& NextObj(void*& ptr)
{return *static_cast<void**>(ptr);
}inline static void* SystemAlloc(size_t kpage)
{void* ptr = nullptr;
#ifdef _WIN32ptr = VirtualAlloc(nullptr, PAGESIZE*kpage, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);if(ptr == NULL)throw std::bad_alloc();
#elseptr = mmap(nullptr, PAGESIZE*kpage, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if(ptr == MAP_FAILED)throw std::bad_alloc();
#endifreturn ptr;
}inline static void SystemFree(void* ptr, size_t kpage)
{if(ptr == nullptr)return;
#ifdef _WIN32VirtualFree(ptr, 0, MEM_RELEASE);
#elsemunmap(ptr, kpage*PAGESIZE);
#endif
}/*** @brief 不可拷贝的基类* * 这个类提供了禁止拷贝语义的基础功能,同时允许移动语义。* 常用于需要防止意外拷贝的资源管理类(如单例、资源句柄等)。* * 设计特点:* 1. 保护构造函数:只能被派生类访问* 2. 删除拷贝构造函数和拷贝赋值运算符:防止拷贝* 3. 默认移动构造函数和移动赋值运算符:允许移动* 4. 虚析构函数:确保正确的多态销毁*/
class NonCopyable 
{
protected:// 保护构造函数,只能被派生类实例化NonCopyable() = default;// 虚析构函数,确保派生类正确析构virtual ~NonCopyable() = default;// 删除拷贝操作,防止对象拷贝NonCopyable(const NonCopyable&) = delete;NonCopyable& operator=(const NonCopyable&) = delete;// 允许移动操作,支持资源转移NonCopyable(NonCopyable&&) = default;NonCopyable& operator=(NonCopyable&&) = default;
};

freelist类

#pragma once  // 防止头文件被重复包含
#include "Log.hpp"
#include "common.h"  // 包含通用定义,可能包含NextObj、Log等
#include <cstddef>/*** @brief 自由链表类,用于管理固定大小的内存块* * 自由链表是内存池的核心组件,用于高效地分配和回收固定大小的内存块。* 每个FreeList实例管理一种特定大小的内存块。*/
class FreeList : public NonCopyable
{
public:/*** @brief 构造函数,初始化自由链表* * 初始化一个空的自由链表,设置初始最大批量和当前大小为0。*/FreeList(): _freelist(nullptr)    // 链表头指针初始化为空, _maxsize(1)           // 初始最大批量大小为1, _size(0)              // 当前链表中的内存块数量为0{}/*** @brief 将内存块压入自由链表(回收内存)* @param ptr 要回收的内存块指针* * 操作步骤:* 1. 检查指针有效性* 2. 将新内存块插入链表头部* 3. 更新链表大小计数* * 链表结构变化:* 原链表: A → B → C → nullptr* 插入X后: X → A → B → C → nullptr*/void Push(void* ptr){if(!ptr)  // 安全检查:空指针检查{Log(WARNING) << "ptr is null";  // 记录警告日志return;}NextObj(ptr) = _freelist;  // 将新块的下一个指针指向当前链表头_freelist = ptr;           // 更新链表头为新块_size++;                   // 增加链表大小计数}void PushRange(void* start, void* end, size_t num){if(start == nullptr || end == nullptr){Log(LogModule::WARNING) << "PushRange null!";return;}NextObj(end) = _freelist;_freelist = start;_size += num;}/*** @brief 从自由链表弹出内存块(分配内存)* @return void* 分配的内存块指针,如果链表为空返回nullptr* * 操作步骤:* 1. 检查链表是否为空* 2. 移除链表头部的内存块* 3. 更新链表大小计数* 4. 返回分配的内存块* * 链表结构变化:* 原链表: A → B → C → nullptr* 弹出A后: B → C → nullptr*/void* Pop(){if(!_freelist)  // 检查链表是否为空{Log(WARNING) << "freelist is null! size: " << _size;  // 记录警告日志return nullptr;}void* ret = _freelist;          // 保存要返回的内存块_freelist = NextObj(_freelist); // 将链表头指向下一个块_size--;                        // 减少链表大小计数return ret;                     // 返回分配的内存块}// 从自由链表中批量取出指定数量的内存块// 参数说明://   start - 输出参数,用于接收取出的内存块链表的头指针//   end   - 输出参数,用于接收取出的内存块链表的尾指针//   num   - 需要取出的内存块数量void PopRange(void*& start, void*& end, size_t num){if(!_freelist)  // 检查链表是否为空{Log(WARNING) << "freelist is null! size: " << _size;  // 记录警告日志return;}// 1. 初始化:将链表头作为起始节点start = _freelist;  // start指向第一个节点end = start;        // end先暂时指向第一个节点// 2. 调整需要取出的数量(因为已经包含了start指向的第一个节点)num--;  // 已经算上了start指向的第一个节点,所以数量减1_size--;// 示例:原链表为 1 -> 2 -> 3 -> null,若num=3// 初始状态:start=1, end=1, num变为2// 循环中会找到第3个节点,最终end=3// 3. 遍历链表,找到第num个节点作为结束节点// 循环条件:下一个节点存在 且 还没取够num个节点while(NextObj(end) && num){end = NextObj(end);  // 移动end指针到下一个节点_size--;num--;}// 4. 更新自由链表的头指针:跳过已取出的节点// 原自由链表从start到end的节点被取出后,新的头指针应指向end的下一个节点_freelist = NextObj(end);// 5. 切断取出的链表与原链表的联系:将end的next置空NextObj(end) = nullptr;}/*** @brief 检查自由链表是否为空* @return bool true表示链表为空,false表示有可用内存块*/bool Empty(){return _size == 0;  // 通过大小计数判断是否为空}/*** @brief 获取当前设置的最大批量大小* @return size_t 最大批量大小值* * 这个值用于控制从中央缓存批量获取内存块的数量:* - 当链表空时,一次性获取_maxsize个内存块* - 动态调整策略可以根据使用频率优化这个值*/size_t Maxsize(){return _maxsize;}void AddMaxsize(size_t n){_maxsize += n;}/*** @brief 获取当前链表中内存块的数量* @return size_t 当前链表中的内存块数量* * 用于监控和统计:* - 判断是否需要向中央缓存申请更多内存块* - 判断是否需要归还多余内存块到中央缓存*/size_t Size(){return _size;}/*** @brief 析构函数* * 使用默认析构函数,因为:* 1. FreeList只管理内存块指针,不负责内存块的分配和释放* 2. 实际的内存管理由上层的内存池组件负责* 3. 链表中的内存块会在中央缓存或页堆中被最终释放*/~FreeList() = default;private:void* _freelist;   // 链表头指针,指向第一个空闲内存块size_t _maxsize;   // 最大批量大小,控制从中央缓存获取内存块的数量size_t _size;      // 当前链表中内存块的数量// 注意:这个类没有拷贝构造函数和赋值运算符// 因为自由链表通常不应该被拷贝,每个FreeList管理特定的内存块集合
};

lockguard类(未用)

#ifndef __LOCKGUARD_HPP__
#define __LOCKGUARD_HPP__
#include<pthread.h>namespace LockGuardModule
{class Lockguard{public:Lockguard(pthread_mutex_t& mutex): _mutex(mutex){pthread_mutex_lock(&_mutex);}~Lockguard(){pthread_mutex_unlock(&_mutex);}private:pthread_mutex_t& _mutex;};
}#endif

Log类

#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <ctime>
#include <string>
#include <pthread.h>
#include <sstream>
#include <fstream>
#include <filesystem>
#include <unistd.h>
#include <memory>
#include "Mutex.hpp"namespace LogModule
{using namespace MutexModule;const std::string default_path = "./log/";const std::string default_file = "log.txt";enum LogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};static std::string LogLevelToString(LogLevel level) {switch (level){case DEBUG:return "DEBUG";case INFO:return "INFO";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return "UNKNOWN";}}static std::string GetCurrentTime(){std::time_t time = std::time(nullptr);struct tm stm;localtime_r(&time, &stm);char buff[128];snprintf(buff, sizeof(buff), "%4d-%02d-%02d-%02d-%02d-%02d",stm.tm_year + 1900,stm.tm_mon + 1,stm.tm_mday,stm.tm_hour,stm.tm_min,stm.tm_sec);return buff;}class Logstrategy{public:virtual ~Logstrategy() = default;virtual void syncLog(std::string &message) = 0;};class ConsoleLogstrategy : public Logstrategy{public:void syncLog(std::string &message) override{std::cerr << message << std::endl;}~ConsoleLogstrategy() override{}};class FileLogstrategy : public Logstrategy{public:FileLogstrategy(std::string filepath = default_path, std::string filename = default_file){_mutex.Lock();_filepath = filepath;_filename = filename;if (std::filesystem::exists(filepath)) // 检测目录是否存在,存在则返回{_mutex.Unlock();return;}   try{// 不存在则递归创建(复数)目录std::filesystem::create_directories(filepath);}catch (const std::filesystem::filesystem_error &e){// 捕获异常并打印std::cerr << e.what() << '\n';}_mutex.Unlock();}void syncLog(std::string &message) override{_mutex.Lock();std::string path =_filepath.back() == '/' ? _filepath + _filename : _filepath + "/" + _filename;std::ofstream out(path, std::ios::app);if (!out.is_open()){_mutex.Unlock();std::cerr << "file open fail!" << '\n';return;}out << message << '\n';_mutex.Unlock();out.close();}~FileLogstrategy(){}private:std::string _filepath;std::string _filename;Mutex _mutex;};class Log{public:Log(){_logstrategy = std::make_unique<ConsoleLogstrategy>();}void useconsolestrategy(){_logstrategy = std::make_unique<ConsoleLogstrategy>();printf("转换控制台策略!\n");}void usefilestrategy(){_logstrategy = std::make_unique<FileLogstrategy>();printf("转换文件策略!\n");}class LogMessage{public:LogMessage(LogLevel level, std::string file, int line, Log &log): _loglevel(level), _time(GetCurrentTime()), _file(file), _pid(getpid()), _line(line),_log(log){std::stringstream ss;ss << "[" << _time << "] "<< "[" << LogLevelToString(_loglevel) << "] "<< "[" << _pid << "] "<< "[" << _file << "] "<< "[" << _line << "] "<< "- ";_loginfo = ss.str();}template <typename T>LogMessage &operator<<(const T &t){std::stringstream ss;ss << _loginfo << t;_loginfo = ss.str();//printf("重载<<Logmessage!\n");return *this;}~LogMessage(){//printf("析构函数\n");if (_log._logstrategy){//printf("调用打印.\n");_log._logstrategy->syncLog(_loginfo);}}private:LogLevel _loglevel;std::string _time;pid_t _pid;std::string _file;int _line;std::string _loginfo;Log &_log;};LogMessage operator()(LogLevel level, std::string filename, int line){return LogMessage(level, filename, line, *this);}~Log(){}private:std::unique_ptr<Logstrategy> _logstrategy;};static Log logger;#define Log(type) logger(type, __FILE__, __LINE__)#define ENABLE_LOG_CONSOLE_STRATEGY() logger.useconsolestrategy()
#define ENABLE_LOG_FILE_STRATEGY() logger.usefilestrategy()
}#endif

linux mutex类封装

#ifndef __MUTEX_HPP__
#define __MUTEX_HPP__
#include<pthread.h>
#include<unistd.h>
#include<iostream>namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){pthread_mutex_lock(&_mutex);}void Unlock(){pthread_mutex_unlock(&_mutex);}pthread_mutex_t& Getmutex(){return _mutex;}~Mutex(){pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t _mutex;};
}#endif

objectpool类(预备)

#pragma once
#include "common.h"template<class T>
class ObjectPool : public NonCopyable
{public:ObjectPool(): _memory(nullptr), _leftBytes(0), _freeList(nullptr){}T* New(){T* obj = nullptr;// 如果⾃由链表有对象,直接取⼀个if (_freeList){obj = (T*)_freeList;_freeList = *((void**)_freeList);}else{if (_leftBytes < sizeof(T)){_leftBytes = 32 * 4 * 1024;_memory = (char*)malloc(_leftBytes);_memoryblocks.push_back(static_cast<void*>(_memory));if (_memory == nullptr){throw std::bad_alloc();}}obj = (T*)_memory;size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) :sizeof(T);_memory += objSize;_leftBytes -= objSize;}// 使⽤定位new调⽤T的构造函数初始化new(obj)T;return obj;}void Delete(T* obj){// 显⽰调⽤的T的析构函数进⾏清理obj->~T();// 头插到freeList*((void**)obj) = _freeList;_freeList = obj;}~ObjectPool(){for(void* ptr : _memoryblocks)free(ptr);}private:std::vector<void*> _memoryblocks;char* _memory;size_t _leftBytes;void* _freeList;
};

sizecalss类

#pragma once
// 引入依赖头文件:日志工具、通用配置、基础类型定义
#include "Log.hpp"
#include "common.h"
#include <cstddef>/*** 内存大小对齐规则说明(核心目标:控制内碎片浪费≤10%)* 按需求内存大小划分 5 个区间,每个区间采用不同的对齐粒度,对应不同的自由链表组:* 1. 区间 [1, 128] 字节:8 字节对齐 → 对应 freelist 索引 [0, 16) → 共 16 个链表* 2. 区间 [129, 1024] 字节:16 字节对齐 → 对应 freelist 索引 [16, 72) → 共 56 个链表* 3. 区间 [1025, 8192] 字节(8*1024):128 字节对齐 → 对应 freelist 索引 [72, 128) → 共 56 个链表* 4. 区间 [8193, 65536] 字节(64*1024):1024 字节对齐 → 对应 freelist 索引 [128, 184) → 共 56 个链表* 5. 区间 [65537, 262144] 字节(256*1024):8192 字节对齐(8*1024)→ 对应 freelist 索引 [184, 208) → 共 24 个链表
*/// SizeClass 类:封装内存大小对齐、索引计算、批量申请数量计算的静态方法(无需实例化)
class SizeClass
{
public:// 核心方法1:将输入的内存大小 bytes 向上对齐到对应区间的标准大小// 返回值:对齐后的标准大小(失败返回 -1)static inline int RoundUp(size_t bytes){// 区间1:[1, 128] 字节 → 8字节对齐if(betweenNum(bytes, 1, 128)){return _RoundUp(bytes, 8);}// 区间2:[129, 1024] 字节 → 16字节对齐(注意:betweenNum 是左闭右开,故上限用 1024)else if(betweenNum(bytes, 128+1, 1024)){return _RoundUp(bytes, 16);}// 区间3:[1025, 8192] 字节 → 128字节对齐(8*1024=8192)else if(betweenNum(bytes, 1024+1, 8*1024)){return _RoundUp(bytes, 128);}// 区间4:[8193, 65536] 字节 → 1024字节对齐(64*1024=65536)else if(betweenNum(bytes, 8*1024+1, 64*1024)){return _RoundUp(bytes, 1024);}// 区间5:[65537, 262144] 字节 → 8192字节对齐(256*1024=262144)else if(betweenNum(bytes, 64*1024+1, 256*1024)){return _RoundUp(bytes, 1024*8);}// 超出支持的最大区间(>256KB)→ 输出警告并返回 -1else {Log(WARNING) << "size invalid!";return -1;}}// 核心方法2:根据输入的内存大小 bytes,计算其对齐后大小对应的自由链表索引// 返回值:自由链表的索引(失败返回 -1)static inline int Index(size_t bytes){// 各区间对应的自由链表数量(与注释中的链表数量一一对应)// groupnum[0]:区间1的链表数(16);groupnum[1]:区间2的链表数(56);以此类推std::vector<int> groupnum = {16, 56, 56, 56, 14};// 区间1:[1, 128] 字节 → 计算该区间内的子索引(align_shift=3 对应 2^3=8 字节对齐)if(betweenNum(bytes, 1, 128)){return _Index(bytes, 3);}// 区间2:[129, 1024] 字节 → 先减去区间1的最大值(128),再算子索引,最后加上前一区间的总链表数(16)else if(betweenNum(bytes, 128+1, 1024)){return _Index(bytes-128, 4) + groupnum[0];  // align_shift=4 对应 2^4=16 字节对齐}// 区间3:[1025, 8192] 字节 → 减去区间2的最大值(1024),算子索引,加上前两区间总链表数(16+56=72)else if(betweenNum(bytes, 1024+1, 8*1024)){return _Index(bytes-1024, 7) + groupnum[0] + groupnum[1];  // align_shift=7 对应 2^7=128 字节对齐}// 区间4:[8193, 65536] 字节 → 减去区间3的最大值(8192),算子索引,加上前三区间总链表数(16+56+56=128)else if(betweenNum(bytes, 8*1024+1, 64*1024)){return _Index(bytes-8*1024, 10) + groupnum[0] + groupnum[1] + groupnum[2];  // align_shift=10 对应 2^10=1024 字节对齐}// 区间5:[65537, 262144] 字节 → 减去区间4的最大值(65536),算子索引,加上前四区间总链表数(16+56+56+56=184)else if(betweenNum(bytes, 64*1024+1, 256*1024)){return _Index(bytes-64*1024, 13) + groupnum[0] + groupnum[1] + groupnum[2] + groupnum[3];  // align_shift=13 对应 2^13=8192 字节对齐}// 超出支持的最大区间 → 输出警告并返回 -1else {Log(WARNING) << "size invalid!: " << bytes;return -1;}}// 核心方法3:计算 ThreadCache 向 CentralCache 批量申请内存块的数量(慢开始算法的基础)// 逻辑:内存块越小,单次申请数量越多;内存块越大,单次申请数量越少,同时限制上下限(2~512)static size_t NumMoveSize(size_t bytes){// 合法性检查:字节数为0 或 超过 ThreadCache 支持的最大字节数(MAX_BYTES,通常为256KB)if(bytes == 0 || bytes > MAX_BYTES){Log(LogModule::WARNING) << "bytes invalid!";return 0;}// 基础计算:用最大支持字节数(MAX_BYTES)除以当前字节数,得到理论申请数量(确保总申请内存不超标)size_t ret = MAX_BYTES / bytes;// 下限:单次申请数量至少2个(避免频繁申请)if(ret < 2){ret = 2;}// 上限:单次申请数量最多512个(避免一次性申请过多内存导致浪费)if(ret > 512){ret = 512;}return ret;}static size_t NumMovePage(size_t size){if(size == 0 || size > MAX_BYTES){Log(LogModule::WARNING) << "size invalid!";return 0;}size_t num = NumMoveSize(size);size_t allsize = size * num;size_t pages = allsize / PAGESIZE;if(pages == 0){pages = 1;}return pages;}private:// 辅助方法1:通用的向上对齐实现(仅支持 align 为 2 的幂次方的情况)// 公式逻辑:(bytes + align - 1) 先将 bytes 补到下一个 align 的倍数,再通过 & (~(align-1)) 清除低位冗余static size_t _RoundUp(size_t bytes, size_t align){return (bytes + align - 1) & (~(align - 1));}// 辅助方法2:计算某区间内的子索引(基于对齐粒度的移位运算,align_shift 是对齐粒度的2的幂次)// 逻辑:等价于 (向上取整(bytes / 对齐粒度) ) - 1(索引从0开始)// 示例:bytes=9,align_shift=3(8字节对齐)→ (9+8-1)>>3 = 16>>3=2 → 2-1=1 → 子索引1static size_t _Index(size_t bytes, size_t align_shift){return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;}// 辅助方法3:判断数值 num 是否在 [low, high] 区间内(左闭右闭,适配区间划分逻辑)static bool betweenNum(int num, int low, int high){return (num >= low && num <= high) ? true : false;}
};

span类

#pragma once
#include "common.h"
#include <cstddef>struct Span
{void ReSet(){_freelist = nullptr;_prev = nullptr;_next = nullptr;_isUse = false;_size = 0;_usecount = 0;}// 页ID,标识该Span管理的内存页在内存池中的唯一编号// 初始值为0,表示未分配具体的页PageID _pageid = 0;// 该Span所管理的内存页数量// 例如:_n = 5 表示当前Span管理了5个连续的内存页size_t _n = 0;// 双向链表指针,指向下一个Span节点Span* _next = nullptr;// 双向链表指针,指向前一个Span节点// (_next和_prev用于将多个Span节点组织成双向链表,便于管理)Span* _prev = nullptr;// 该Span管理的内存块大小(每个小块的字节数)// 例如:_size = 16 表示该Span将内存页分割成多个16字节的小块size_t _size = 0;// 已使用的内存块数量// 用于跟踪该Span中当前被分配出去的小块数量size_t _usecount = 0;// 指向空闲内存块链表的头指针// 管理当前Span中所有未被使用的小块内存void* _freelist = nullptr;// 标记当前Span是否正在被使用// true表示该Span中有内存块被分配使用,false表示完全空闲bool _isUse = false;
};

spanlist类

#pragma once
// 引入依赖头文件:日志工具、Span结构体定义、通用配置、标准互斥锁库
#include "Log.hpp"
#include "Span.hpp"
#include "common.h"
#include <mutex>// SpanList 类:管理Span的双向循环链表,继承NonCopyable禁止拷贝(避免链表所有权混乱)
class SpanList : public NonCopyable
{
public:// 构造函数:初始化双向循环链表(创建头节点,头节点的前后指针均指向自身)SpanList(){_head = new Span;       // 创建头节点(不存储实际Span数据,仅用于链表遍历)_head->_next = _head;   // 头节点的next指针指向自身(循环特性)_head->_prev = _head;   // 头节点的prev指针指向自身(循环特性)}void lock(){_mutex.lock();}void unlock(){_mutex.unlock();}// 迭代器相关:获取链表的起始位置(头节点的下一个节点,即第一个有效Span)Span* begin(){return _head->_next;}// 迭代器相关:获取链表的结束位置(头节点本身,作为遍历终止标志)Span* end(){return _head;}// 头部插入:将Span节点插入到链表头部(头节点之后)void PushFront(Span* span){insert(_head, span);  // 调用insert方法,在头节点后插入新节点}// 头部删除:从链表头部弹出一个Span节点(头节点的下一个节点)Span* PopFront(){Span* span = _head->_next;  // 获取头节点的下一个节点(待弹出节点)if (span == _head)          // 若待弹出节点是头节点,说明链表为空{Log(LogModule::DEBUG) << "Pop fail!";return nullptr;         // 返回空指针(删除失败)}erase(span);                // 调用erase方法删除该节点return span;                // 返回弹出的Span节点}// 插入操作:在指定位置pos之前插入新节点newspan(双向链表插入逻辑)void insert(Span* pos, Span* newspan){// 合法性检查:插入位置或新节点为空,输出日志并返回if(pos == nullptr || newspan == nullptr){Log(LogModule::DEBUG) << "nullptr can not insert!";return;}// 插入逻辑:调整指针,将newspan插入到pos的前驱节点与pos之间// 原关系:prev -> pos// 新关系:prev -> newspan -> posSpan* prev = pos->_prev;    // 获取pos的前驱节点prev->_next = newspan;      // 前驱节点的next指向新节点newspan->_prev = prev;      // 新节点的prev指向前驱节点newspan->_next = pos;       // 新节点的next指向pospos->_prev = newspan;       // pos的prev指向新节点}// 删除操作:删除指定的Span节点target(双向链表删除逻辑)bool erase(Span* target)   //TODO:待确认是否需要释放target的内存(当前仅从链表移除){// 合法性检查:目标节点为空,或目标节点的前后指针异常,输出日志并返回失败if(target == nullptr || target->_next == nullptr || target->_prev == nullptr){Log(LogModule::DEBUG) << "nullptr can not erase!";return false;}// 删除逻辑:调整目标节点前后节点的指针,断开目标节点与链表的连接// 原关系:prev -> target -> next// 新关系:prev -> nextSpan* prev = target->_prev;  // 获取目标节点的前驱Span* next = target->_next;  // 获取目标节点的后继prev->_next = next;          // 前驱的next指向后继next->_prev = prev;          // 后继的prev指向前驱// 注意:此处仅从链表移除target,未释放target的内存(需外部根据业务逻辑处理)target->_prev = nullptr;target->_next = nullptr;return true;  // 返回删除成功}// 判空操作:判断链表是否为空(头节点的next指针指向自身即为空)bool empty(){return _head->_next == _head;}// 析构函数:释放链表头节点的内存(避免内存泄漏)~SpanList(){delete _head;}private:// 链表头节点:双向循环链表的哨兵节点,不存储实际Span数据,仅用于简化链表操作Span* _head;public:// 互斥锁:保护链表的并发访问(多线程操作时需加锁,避免数据竞争)std::mutex _mutex;
};

thread cache类

#pragma once
// 引入依赖头文件:分别提供日志打印、通用配置、自由链表管理、内存大小计算、中心缓存支持
#include "Log.hpp"          // 日志工具:用于打印警告、调试等日志信息(如非法参数警告)
#include "common.h"         // 通用配置:包含 MAX_BYTES(ThreadCache 处理的最大内存大小)、NFREELISTS(自由链表数量)等宏定义
#include "FreeList.hpp"     // FreeList类定义:管理同大小内存块的链表(提供Pop、Push、PopRange等操作)
#include "SizeClass.hpp"    // SizeClass工具类:提供内存大小对齐、计算哈希索引、批量申请数量等核心算法
#include "Centralcache.hpp" // CentralCache类定义:ThreadCache的上层缓存,用于补充本地缓存不足
#include <algorithm>        // 标准算法库:用于std::min函数(计算向CentralCache申请的批量大小)
#include <cstddef>          // 标准类型库:提供size_t等基础类型(表示内存大小、数量)// ThreadCache 类:高并发内存池的最下层缓存(线程本地缓存),继承NonCopyable禁止拷贝构造和赋值
// 核心设计目标:每个线程拥有独立实例,避免线程间锁竞争,最大化内存分配/释放效率
class ThreadCache : public NonCopyable
{
public:// 构造函数:默认空实现// 原因:成员变量_freelists(自由链表数组)会自动调用FreeList的默认构造函数初始化,无需额外操作ThreadCache(){}// 内存分配接口:对外提供的核心分配函数,输入需求内存大小,返回分配的内存指针// 参数:size - 应用程序需要的内存大小(如10字节、25字节)// 返回值:成功返回非空内存指针,失败(大小非法)返回nullptrvoid* Allocate(size_t size){void* obj = nullptr;  // 存储最终分配的内存块指针,初始化为空(默认分配失败)// 1. 合法性检查:排除无效内存大小(≤0 或超过ThreadCache处理上限MAX_BYTES)// ThreadCache仅处理小内存(通常MAX_BYTES设为256KB或512KB),大内存直接走PageCacheif(size <= 0 || size > MAX_BYTES){Log(WARNING) << "size invalid!";  // 打印警告日志(提示非法大小)return obj;  // 返回空指针(分配失败)}// 2. 内存大小对齐:将需求大小向上对齐到预设的标准大小(如10字节对齐到16字节)// 目的:避免内存碎片,统一管理同规格内存块(FreeList按对齐后的大小分类)int alignsize = SizeClass::RoundUp(size);// 3. 计算哈希索引:根据对齐后的大小,找到对应的FreeList在_freelists数组中的位置// 例如:对齐后16字节的内存块,对应索引为1(具体索引规则由SizeClass定义)int index = SizeClass::Index(size);// 4. 优先从本地自由链表分配:若对应链表非空,直接从链表头部弹出一个内存块// 线程本地操作,无锁竞争,效率极高(核心性能优势)if(_freelists[index].Empty() == false){obj = _freelists[index].Pop();}// 5. 本地缓存不足:当前FreeList为空,需向上层CentralCache批量申请内存块补充else{obj = FetchFromCentralCache(index, alignsize);}return obj;  // 返回分配的内存块(成功非空,失败为空)}// 内存释放接口:对外提供的核心释放函数,输入待释放内存指针和其原始大小// 参数:ptr - 待释放的内存指针(必须是通过ThreadCache::Allocate分配的指针);size - 该内存块的原始申请大小void Deallocate(void* ptr, size_t size){// 1. 合法性检查:排除无效大小(≤0或超MAX_BYTES)、空指针(避免非法访问)if(size <= 0 || size > MAX_BYTES || ptr == nullptr){Log(WARNING) << "size invalid or ptr is null!";  // 打印警告日志(提示非法参数)return;  // 直接返回(释放失败,无操作)}// (可选)确保大小是对齐后的标准大小(防止因外部传入错误原始大小导致的索引错误)size = SizeClass::RoundUp(size);// 2. 计算哈希索引:根据原始大小找到对应的FreeList位置(无需对齐,因分配时已对齐,原始大小可反推索引)int index = SizeClass::Index(size);// 3. 将内存块归还给本地自由链表:线程本地操作,无锁,直接将块插入链表头部_freelists[index].Push(ptr);// 4. 缓存囤积检查:当FreeList长度超过其最大限制时,将多余内存块归还给CentralCache// 目的:避免单个线程囤积过多内存,导致其他线程内存不足(平衡线程间内存分配)if(_freelists[index].Size() >= _freelists[index].Maxsize()){
#if _DEBUG == 1//Log(LogModule::DEBUG) << "回收机制开启!" << "size/maxsize: " //<< _freelists[index].Size() << "/" << _freelists[index].Maxsize();
#endifListTooLong(_freelists[index], size);}}private:// 核心辅助函数:向CentralCache批量申请内存块,补充本地FreeList的缓存// 参数:index - 本地FreeList的哈希索引;size - 单个内存块的对齐后大小// 返回值:成功返回一个内存块指针(给当前分配请求),失败返回nullptrvoid* FetchFromCentralCache(size_t index, size_t size){// 慢开始反馈调节算法:动态控制向CentralCache申请的批量大小,平衡"申请效率"与"内存利用率"// 核心逻辑拆解:// 1. 初始申请量小:避免线程首次申请就占用大量内存(减少浪费);// 2. 动态增长:若该大小内存持续被需求(本地缓存频繁耗尽),申请量逐步增加(直至上限),减少申请次数;// 3. 大小适配:内存块越大,单次申请量越小(大内存浪费风险高);内存块越小,单次申请量越大(小内存分配频繁,减少系统调用)// 计算本次申请量:取"本地FreeList当前最大限制"与"SizeClass推荐的批量大小"的较小值(避免超量申请)size_t batchNum = std::min(_freelists[index].Maxsize(), SizeClass::NumMoveSize(size));// 申请量达上限时动态扩容:若本次申请量等于本地FreeList的当前最大限制,说明该大小内存需求频繁// 下次允许申请更多(最大限制+1),进一步减少后续申请次数(反馈调节的核心)if (batchNum == _freelists[index].Maxsize()) {_freelists[index].AddMaxsize(1);  // 最大限制+1(如从5→6,下次可申请6个块)}// 1. 获取CentralCache单例实例:CentralCache是全局唯一的,通过GetInstance()获取CentralCache* ccins = CentralCache::GetInstance();void* start = nullptr;  // 存储从CentralCache获取的内存块链表的头指针void* end = nullptr;    // 存储从CentralCache获取的内存块链表的尾指针// 2. 向CentralCache批量申请内存块:传入申请量、单个块大小,返回实际获取的块数量(可能小于申请量,因CentralCache也可能不足)size_t actualNum = ccins->FetchRangeObj(start, end, batchNum, size);// 3. 处理申请结果:实际获取数量<1(申请失败),打印调试日志并返回空if(actualNum <  1){Log(LogModule::DEBUG) << "actualnum invalid";  // 调试日志(排查CentralCache内存不足问题)return nullptr;}// 4. 仅获取到1个内存块:直接返回给调用者(无需加入本地FreeList,减少链表操作开销)else if(actualNum == 1){return start;}// 5. 获取到多个内存块:将"第一个块之外的所有块"加入本地FreeList(第一个块返回给当前分配请求)// NextObj(start):工具函数,通过内存块头部存储的next指针,获取start的下一个块(FreeList链表的节点结构)_freelists[index].PushRange(NextObj(start), end, actualNum-1);return start;  // 返回第一个内存块给调用者(满足当前Allocate请求)}// 核心辅助函数:处理"本地FreeList过长"的逻辑(将多余内存块归还给CentralCache,避免缓存囤积)// 参数:list - 过长的FreeList引用;size - 该FreeList管理的单个内存块大小void ListTooLong(FreeList& list, size_t size){// 实现逻辑:从FreeList中取出"超过最大限制的部分"(此处简化为取出最大限制数量的块),归还给CentralCachevoid* start = nullptr;  // 待归还的内存块链表头void* end = nullptr;    // 待归还的内存块链表尾// 从FreeList中批量弹出"最大限制数量"的块(具体弹出数量可根据策略调整,如弹出一半)list.PopRange(start, end, list.Maxsize());// 调用CentralCache的接口,将弹出的块链表归还给中心缓存(CentralCache会进一步合并或管理)CentralCache::GetInstance()->RealseListToSpans(start, size);}public:// 析构函数:默认空实现// 说明:若后续需在线程退出时主动释放本地缓存(如将所有块归还给CentralCache),需在此补充逻辑;// 目前依赖程序退出时操作系统回收内存,简化设计(高并发场景下线程退出频繁,主动释放可能增加开销)~ThreadCache(){}private:// 核心成员变量:哈希桶结构的FreeList数组,每个桶对应一类对齐后的内存大小// 例如:索引0对应8字节块,索引1对应16字节块...(具体对应关系由SizeClass定义)// NFREELISTS:FreeList的数量,与SizeClass中"对齐大小的类别数量"一致(确保每个对齐大小都有对应桶)FreeList _freelists[NFREELISTS];
};/*** 线程局部的ThreadCache单例指针:高并发内存分配器的核心设计(线程隔离)* 关键修饰符详细说明:* 1. static:静态存储期,指针本身在程序整个生命周期内存在(不会因函数调用结束而销毁);* 2. thread_local:线程局部存储(TLS),每个线程拥有该指针的独立副本(线程A的指针与线程B的指针互不干扰);* 3. ThreadCache*:指针类型,指向当前线程专属的ThreadCache实例;* 4. = nullptr:初始化为空指针(首次使用时需调用构造函数创建实例,避免提前初始化浪费内存);* * 核心作用:* - 确保每个线程操作自己的ThreadCache,完全避免线程间锁竞争(分配/释放无需加锁);* - 线程首次调用Allocate时,再创建ThreadCache实例(懒初始化),节省内存(无业务的线程不占用缓存内存);
*/
static thread_local ThreadCache* pTLSThreadCache = nullptr;

central cache类

#pragma once
// 引入依赖头文件:分别提供日志打印、内存大小计算、Span结构、通用配置、Span链表管理、页缓存接口及互斥锁支持
#include "Log.hpp"        // 日志工具:用于打印调试、错误等日志信息
#include "SizeClass.hpp"  // SizeClass工具类:提供内存大小与页数转换、哈希索引计算等功能
#include "Span.hpp"       // Span类定义:封装内存块的核心结构(包含页ID、自由链表、使用计数等)
#include "common.h"       // 通用配置:包含NFREELISTS(哈希桶数量)、PAGE_SHIFT(页偏移量)、PAGESIZE(页大小)等宏定义
#include "Spanlist.hpp"   // SpanList类定义:管理Span的链表结构(内置桶锁,提供PushFront、erase等操作)
#include "PageCache.hpp"  // PageCache类定义:上层页缓存接口,供CentralCache申请大内存块
#include <cstddef>        // 标准类型库:提供size_t等基础类型
#include <cstdint>        // 标准整数类型库:提供uint64_t等精确宽度类型(用于地址计算)
#include <mutex>          // 互斥锁库:提供std::mutex,保证单例初始化及桶操作的线程安全// CentralCache 类:高并发内存池的中间层缓存,继承NonCopyable禁止拷贝构造和赋值(确保全局唯一实例)
// 核心定位:作为ThreadCache与PageCache的桥梁,平衡线程间内存分配,减少页缓存的直接访问
class CentralCache : NonCopyable
{
private:// 私有构造函数:单例模式核心,禁止外部直接实例化(确保仅通过GetInstance()创建实例)CentralCache(){}public:// 单例模式接口:获取CentralCache全局唯一实例(双检锁DCLP,兼顾线程安全与效率)static CentralCache* GetInstance(){static CentralCache* instance = nullptr;  // 静态指针存储单例实例,初始为nullptr// 第一次检查:未加锁,快速判断实例是否已存在(避免每次调用都加锁,提升高并发场景效率)if(instance == nullptr){// 加锁:使用std::lock_guard(RAII机制),作用域结束自动解锁,避免异常导致死锁std::lock_guard<std::mutex> lock(_mutex);// 第二次检查:加锁后再次判断,防止多线程并发时重复初始化(核心线程安全保障)if(instance == nullptr){instance = new CentralCache;  // 初始化单例实例(调用私有构造函数)}}return instance;  // 返回全局唯一实例}// 核心辅助方法:从指定的SpanList中获取一个有空闲内存块的Span// 参数:list - 待查找的Span链表(对应某类大小的内存块);byte_size - 需要的内存块大小(对齐后)// 返回值:成功返回包含空闲块的Span指针,失败返回nullptrSpan* GetOneSpan(SpanList& list, size_t byte_size){// 1. 遍历当前SpanList,查找自由链表(_freelist)非空的Span(即存在空闲内存块的Span)Span* start = list.begin();  // 获取链表头节点while(start != list.end())   // 遍历至链表尾{if(start->_freelist)     // 若当前Span的自由链表非空(有可用块){return start;        // 直接返回该Span}start = start->_next;    // 否则继续遍历下一个Span}// 2. 未找到有空闲块的Span:向PageCache申请新的Span(按内存块大小计算所需页数)PageCache* pc = PageCache::GetInstance();  // 获取PageCache单例// 计算所需页数:根据内存块大小,由SizeClass确定一次向PageCache申请的页数(平衡效率与内存利用率)size_t pages = SizeClass::NumMovePage(byte_size);pc->lock();  // 锁定PageCache(跨线程操作页缓存,需保证线程安全)Span* newspan = pc->NewSpan(pages);  // 从PageCache申请pages页的Spannewspan->_isUse = true;  // 标记Span为"已使用"(区别于PageCache中的空闲Span)newspan->_size = byte_size;pc->unlock();  // 解锁PageCache// 3. 将新Span切割为byte_size大小的小内存块,串联成自由链表(供ThreadCache申请)// 3.1 计算Span的内存地址范围:页ID转换为实际内存地址(页ID左移PAGE_SHIFT位 = 起始地址)char* begin = reinterpret_cast<char*>(static_cast<uint64_t>(newspan->_pageid) << PAGE_SHIFT);char* end = begin + newspan->_n * PAGESIZE;  // 结束地址 = 起始地址 + 总字节数(页数×页大小)// 3.2 切割内存块:从起始地址开始,按byte_size分割,形成单向链表(头插法)while(begin < end){if(end - begin < byte_size)  // 剩余空间不足一个块大小时,停止切割(避免内存越界)break;void* tail = static_cast<void*>(begin);  // 当前块的起始地址NextObj(tail) = newspan->_freelist;      // 当前块的next指针指向Span原自由链表头newspan->_freelist = tail;               // 更新Span的自由链表头为当前块(头插法)begin += byte_size;                      // 移动到下一个块的起始地址}// 4. 将新Span加入当前SpanList,返回该Span供分配list.PushFront(newspan);return newspan;}// 核心对外接口:向ThreadCache批量提供内存块(补充ThreadCache的本地缓存)// 参数://   start/end - 输出参数,存储获取到的内存块链表的头/尾指针(供ThreadCache使用)//   batchNum - ThreadCache期望申请的批量数量//   size - 单个内存块的大小(对齐后)// 返回值:实际获取到的内存块数量(0表示失败)size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size){// 1. 计算size对应的哈希桶索引(与ThreadCache、SizeClass的索引规则一致,确保匹配)size_t index = SizeClass::Index(size);// 2. 加桶锁:仅锁定当前哈希桶的SpanList(_spanlist[index]),而非全局锁// 目的:减少多线程竞争(不同桶的操作可并行),提升并发效率std::lock_guard<std::mutex> lock(_spanlist[index]._mutex);// 3. 获取一个有空闲块的Span(调用GetOneSpan,从当前桶的SpanList中查找或向PageCache申请)Span* span = GetOneSpan(_spanlist[index], size);// 4. 检查Span有效性:Span为空或其自由链表为空(申请失败),日志警告并返回0if(span == nullptr || span->_freelist == nullptr){Log(LogModule::DEBUG) << "GetOneSpan fail!";  // 调试日志(排查内存不足问题)return 0;}// 5. 初始化内存块链表:从Span的自由链表中批量取块size_t actualNum = 1;  // 实际获取的数量(至少1个,初始化为1)start = span->_freelist;  // 链表头指向Span自由链表的第一个块end = start;              // 链表尾初始与头重合(仅1个块时)size_t i = 0;// 6. 批量取块:最多取batchNum个,或取完Span中所有空闲块(有多少拿多少)// 循环条件:未取满batchNum-1个(因已初始化1个)、当前尾块非空、尾块的下一个块非空while (i < batchNum - 1 && end != nullptr && NextObj(end) != nullptr) {end = NextObj(end);    // 尾指针后移到下一个块i++;                   // 计数+1actualNum++;           // 实际数量+1}span->_usecount += actualNum;     // Span的已使用块计数// 7. 更新Span的自由链表:指向剩余未取走的第一个块(若有)span->_freelist = NextObj(end);// 8. 切断内存块链表:将取出的最后一个块的next置空(避免与Span中剩余块关联)NextObj(end) = nullptr;// 9. 返回实际获取的内存块数量(供ThreadCache记录本地缓存数量)return actualNum;}// 核心对外接口:将ThreadCache归还的内存块链表回收至CentralCache,并根据使用情况决定是否合并至PageCache// 参数:start - 待回收的内存块链表头;alignsize - 单个内存块的对齐后大小void RealseListToSpans(void* start, size_t alignsize){// 1. 获取对应的SpanList(根据内存块大小的索引),并加桶锁(确保线程安全)SpanList& spanlist = _spanlist[SizeClass::Index(alignsize)];std::lock_guard<std::mutex> lock(spanlist._mutex);// 2. 遍历内存块链表,逐个归还给对应的Spanwhile (start) {void* next = NextObj(start);  // 先记录下一个块(避免归还后丢失链表)// 2.1 查找当前块所属的Span(通过PageCache的地址映射接口)PageCache* pc = PageCache::GetInstance();Span* span = pc->AddrToSpan(start);if(span == nullptr)  // 地址映射失败(异常情况){Log(LogModule::ERROR) << "span find fail!";  // 错误日志(排查内存块归属问题)return;}// 2.2 将当前块归还给Span的自由链表(头插法)span->_usecount--;               // 已使用块计数-1(表示归还一个块)NextObj(start) = span->_freelist; // 当前块的next指针指向Span原自由链表头span->_freelist = start;          // 更新Span的自由链表头为当前块// 2.3 检查Span是否已完全空闲(所有块都被归还)if(span->_usecount == 0){// 从CentralCache的SpanList中移除该Span(不再管理,准备归还PageCache)spanlist.erase(span);// 重置Span状态(除页ID和页数外,清空其他字段如自由链表、使用计数等)span->ReSet();// 将完全空闲的Span归还给PageCache(PageCache会进行合并,减少碎片)pc->lock();  // 锁定PageCache(跨线程操作页缓存)pc->RealseSpanToPageCache(span);pc->unlock();  // 解锁PageCache}start = next;  // 处理下一个内存块}}// 析构函数:默认空实现// 原因:CentralCache是单例,实例随程序生命周期存在,程序退出时操作系统会回收所有内存,无需手动释放~CentralCache(){}private:// 核心成员变量:哈希桶结构的SpanList数组,每个桶对应一类对齐后的内存块大小// 例如:索引0对应8字节块的Span,索引1对应16字节块的Span...(与ThreadCache的FreeList索引一一对应)// NFREELISTS:桶数量,与SizeClass中"内存块大小的类别数量"一致SpanList _spanlist[NFREELISTS];// 静态互斥锁:仅用于保护单例实例的初始化过程(双检锁中的锁)static std::mutex _mutex;
};// 静态成员变量定义:C++17特性,inline修饰静态成员变量可直接在头文件中定义(避免ODR冲突)
// 作用:初始化CentralCache的静态互斥锁,确保单例初始化的线程安全
inline std::mutex CentralCache::_mutex;

page cache类

#pragma once
// 引入依赖头文件:分别提供日志打印、Span结构定义、Span链表管理、通用配置、标准类型及互斥锁支持
#include "Log.hpp"       // 日志工具:用于打印警告、错误等日志信息
#include "Span.hpp"      // Span类定义:封装页内存块的核心结构(包含页ID、页数、使用状态等)
#include "Spanlist.hpp"  // SpanList类定义:用于管理Span的链表结构(提供PopFront、PushFront、erase等操作)
#include "common.h"      // 通用配置:包含NPAGES(最大支持页数)、PAGE_SHIFT(页偏移量,用于地址转页ID)、PageID(页ID类型)等宏/类型定义
#include <cassert>
#include <cstddef>       // 标准类型库:提供size_t等基础类型
#include <cstdint>
#include <mutex>         // 互斥锁库:提供std::mutex,保证多线程安全
#include <unordered_map> // 哈希表容器:用于页ID到Span的映射(_idtospanmap)// PageCache 类:高并发内存池的顶层缓存(页缓存),继承NonCopyable禁止拷贝构造和赋值(确保全局唯一实例)
class PageCache : public NonCopyable
{
public:// 静态加锁接口:供外部(如CentralCache合并Span时)手动锁定页缓存// 用途:当操作涉及跨多个SpanList桶(如下层释放Span需合并相邻页)时,需先锁定整个PageCache避免线程安全问题static void lock(){_mutex.lock(); // 调用静态互斥锁的lock()方法,独占锁定}// 静态解锁接口:与lock()配对使用,释放PageCache的锁定// 注意:必须在加锁后调用,且确保锁的获取/释放成对,避免死锁static void unlock(){_mutex.unlock(); // 释放静态互斥锁}// 单例模式接口:获取PageCache全局唯一实例(双检锁DCLP,兼顾线程安全与效率)static PageCache* GetInstance(){static PageCache* instance = nullptr;  // 静态指针存储单例实例,初始为nullptr// 第一次检查:未加锁,快速判断实例是否已存在(避免每次调用都加锁,提升高并发场景效率)if(instance == nullptr){// 加锁:使用std::lock_guard(RAII机制),作用域结束自动解锁,避免异常导致死锁std::lock_guard<std::recursive_mutex> lock(_mutex);// 第二次检查:加锁后再次判断,防止多线程并发时重复初始化(核心线程安全保障)if(instance == nullptr){instance = new PageCache;  // 初始化单例实例(调用私有构造函数)}}return instance;  // 返回全局唯一实例}// 核心工具接口:通过内存地址反向映射到对应的Span// 用途:下层(如CentralCache)释放内存时,需通过内存地址找到其所属的Span,才能进行后续合并操作// 参数:addr - 待映射的内存地址(如小块内存的起始地址)// 返回值:成功返回对应的Span指针,失败(地址无映射)返回nullptrSpan* AddrToSpan(void* addr) {if (addr == nullptr) {Log(LogModule::DEBUG) << "AddrToSpan: null pointer";return nullptr;}uintptr_t address = reinterpret_cast<uintptr_t>(addr);PageID id = address >> PAGE_SHIFT;#if _DEBUG == 1Log(LogModule::DEBUG) << "AddrToSpan: addr=" << addr << ", page_id=" << id<< ", map_size=" << _idtospanmap.size();#endifauto it = _idtospanmap.find(id);if(it != _idtospanmap.end()) {// 验证地址确实在Span范围内Span* span = it->second;uintptr_t span_start = span->_pageid << PAGE_SHIFT;uintptr_t span_end = span_start + (span->_n << PAGE_SHIFT);if (address >= span_start && address < span_end) {return span;} else {Log(LogModule::ERROR) << "AddrToSpan: address " << addr << " not in span range [" << (void*)span_start << ", " << (void*)span_end << ")";}}else {Log(LogModule::DEBUG) << "AddrToSpan: page_id " << id << " not found in map";// 尝试查找附近的页/*for (int offset = -5; offset <= 5; offset++) {if (offset == 0) continue;auto nearby_it = _idtospanmap.find(id + offset);if (nearby_it != _idtospanmap.end()) {Log(LogModule::DEBUG) << "Nearby page found at offset " << offset << ": " << (id + offset);}}*/}return nullptr;}// 核心功能接口:分配指定页数的连续物理内存块,返回对应的Span// 参数:pages - 需要分配的连续页数(合法范围:1 ≤ pages ≤ NPAGES-1,NPAGES为配置的最大支持页数)// 返回值:成功返回管理该连续页的Span指针,失败(页数非法/系统分配失败)返回nullptrSpan* NewSpan(size_t pages){// 1. 合法性检查:页数小于1或超过最大支持页数(NPAGES-1),打印警告并返回nullptrif(pages < 1 || pages > NPAGES - 1){Log(LogModule::WARNING) << "pages invalid!"; // 打印警告日志(如页数为0或超过127)return nullptr;}// 2. 优先从对应页数的SpanList桶中分配(精确匹配策略,最高效)Span* obj = nullptr;// 检查第pages号桶(索引=页数)是否非空(存在空闲Span)if(!_spanlist[pages].empty()){// 从桶的头部弹出一个Span(SpanList是带头节点的链表,PopFront()取首元素)return _spanlist[pages].PopFront();}// 3. 无精确匹配的Span:从更大页数的SpanList桶中查找,拆分大Span适配需求// 遍历范围:从pages+1到NPAGES-1(只找比需求大的桶)for(int i = pages + 1; i < NPAGES; i++){// 找到第一个非空的大页数桶if(!_spanlist[i].empty()){// 3.1 取出大Span:从大桶头部弹出一个Span(页数为i)Span* ispan = _spanlist[i].PopFront();PopIdToMap(ispan->_pageid, ispan->_n);// 3.2 创建新Span:用于存储拆分后满足需求的pages页内存obj = new Span;// 3.3 拆分大Span:新Span占前pages页,剩余部分仍为一个独立Span//1000 5 / 3obj->_pageid = ispan->_pageid;  // 新Span的起始页ID = 大Span的起始页ID(前pages页)ispan->_pageid += pages;        // 剩余Span的起始页ID = 原起始页ID + pages(跳过已拆分部分)ispan->_n -= pages;             // 剩余Span的页数 = 原页数i - pagesobj->_n = pages;                // 新Span的页数 = 需求页数pages// 3.4 剩余Span回桶:将拆分后的剩余Span(页数为ispan->_n)放回对应桶中_spanlist[ispan->_n].PushFront(ispan);// 3.5 页ID映射维护:更新哈希表(_idtospanmap),建立页ID到Span的关联// 3.5.1 新Span(已分配,给下层使用):全页映射(所有页ID都关联到该Span)// 目的:下层通过任意页内地址,都能找到所属Span(如释放时AddrToSpan()查询)PushIdToMap(obj->_pageid, obj->_n, obj);// 3.5.2 剩余Span(空闲):仅首尾页ID映射(减少哈希表开销,合并时只需查首尾)// 目的:空闲Span合并时,只需检查前一页(curid-1)和后一页(curid+span->_n)是否为空闲Span的首尾PushIdToMap(ispan->_pageid, ispan->_n, ispan);          // 剩余Span的起始页ID映射//PushIdToMap(ispan->_pageid + ispan->_n - 1, 1, ispan); // 剩余Span的结束页ID映射return obj;  // 返回拆分后满足需求的新Span}//Log(LogModule::DEBUG) << "spanlist-" << i << "-empty!";}// 4. 无任何可用Span:向操作系统申请最大粒度内存(NPAGES-1页),再递归分配// 4.1 调用系统接口分配内存:SystemAlloc是封装的底层接口(如Linux的mmap、Windows的VirtualAlloc)void* ptr = SystemAlloc(NPAGES - 1);// 4.2 地址转页ID:将系统分配的内存起始地址转为起始页IDPageID id = reinterpret_cast<uintptr_t>(ptr) >> PAGE_SHIFT;//Log(LogModule::DEBUG) << "------------id: " << id << "---" << id + NPAGES - 1 - 1;if(reinterpret_cast<uintptr_t>(ptr) % PAGESIZE != 0){Log(LogModule::ERROR) << "ptr unfit!";}
#if _DEBUG == 1Log(LogModule::DEBUG) << "ptr: " << ptr << " id: " << id;
#endif// 4.3 封装为大Span:创建管理该128页内存的SpanSpan* bigspan = new Span;bigspan->_pageid = id;    // 起始页ID = 系统分配内存的页IDbigspan->_n = NPAGES - 1; // 页数 = 最大支持页数(如128)_spanlist[bigspan->_n].PushFront(bigspan);  // 将大Span放入第NPAGES-1号桶(对应127页的桶)PushIdToMap(bigspan->_pageid, bigspan->_n, bigspan);// 4.4 递归调用NewSpan:此时桶中有了大Span,可拆分出需求的pages页return NewSpan(pages);}// 核心功能接口:将下层(CentralCache)释放的Span回收至PageCache,并触发合并// 参数:span - 待回收的Span(已由CentralCache标记为空闲)void RealseSpanToPageCache(Span* span){if(span == nullptr){Log(LogModule::DEBUG) << "span invalid!";return;}MergePage(span); // 调用合并逻辑,将该Span与相邻空闲Span合并(减少内存碎片)}// 析构函数:默认空实现// 原因:PageCache是单例,实例随程序生命周期存在,程序退出时操作系统会回收所有内存,无需手动释放~PageCache(){}private:// 私有辅助接口:批量将页ID范围映射到Span(写入哈希表_idtospanmap)// 参数:begin - 起始页ID,num - 页数量,span - 对应的Span指针void PushIdToMap(PageID begin, size_t num, Span* span){// 遍历从begin开始的num个页ID,逐一建立映射for(PageID i = 0; i < num; i++){_idtospanmap[begin + i] = span;}//Log(LogModule::DEBUG) << "Push id: " << begin << "-" << begin+num-1 << "in map";}// 私有辅助接口:批量删除页ID范围的映射(从哈希表_idtospanmap中 erase)// 参数:begin - 起始页ID,num - 页数量void PopIdToMap(PageID begin, size_t num){// 遍历从begin开始的num个页ID,逐一删除映射for(PageID i = 0; i < num; i++){_idtospanmap.erase(begin + i);}//Log(LogModule::DEBUG) << "Pop id: " << begin << "-" << begin+num-1 << "in map";}// 私有核心逻辑:合并空闲Span(向前合并+向后合并),减少内存碎片// 逻辑:将当前Span与前后相邻的空闲Span合并,形成更大的连续Span,提升后续大页数分配的成功率void MergePage(Span* span){// -------------------------- 第一步:向前合并(合并当前Span的前一个空闲Span)--------------------------while (true) {// 当前Span的起始页ID(用于找前一个页ID)PageID curid = span->_pageid;// 查找前一个页ID(curid-1)是否存在映射(即是否有相邻的前Span)auto it = _idtospanmap.find(curid - 1);// 前一个页ID无映射(无相邻前Span),终止向前合并if(it == _idtospanmap.end()){break;}// 找到前一个页ID对应的Span(prevspan)Span* prevspan = it->second;// 合并条件检查:前Span必须未被使用(_isUse=false),且合并后总页数不超过最大支持页数(NPAGES-1)if(prevspan->_isUse == true || prevspan->_n + span->_n > NPAGES - 1){break; // 不满足合并条件,终止向前合并}PopIdToMap(prevspan->_pageid, prevspan->_n);// 执行合并:将前Span的内存合并到当前Span中span->_pageid = prevspan->_pageid;  // 当前Span的起始页ID = 前Span的起始页ID(向前扩展)span->_n += prevspan->_n;           // 当前Span的页数 = 原页数 + 前Span的页数// 清理前Span:从其所属的SpanList桶中删除,再释放前Span对象bool b = _spanlist[prevspan->_n].erase(prevspan);if(!b){Log(LogModule::ERROR) << "prevspan erase fail!";}delete prevspan; // 释放前Span的堆内存(避免内存泄漏)}// -------------------------- 第二步:向后合并(合并当前Span的后一个空闲Span)--------------------------while (true) {// 当前Span的起始页ID(用于找后一个页ID:当前Span的结束页ID+1 = 起始页ID + 页数)PageID curid = span->_pageid;auto it = _idtospanmap.find(curid + span->_n);// 后一个页ID无映射(无相邻后Span),终止向后合并if(it == _idtospanmap.end()){break;}// 找到后一个页ID对应的Span(nextspan)Span* nextspan = it->second;// 合并条件检查:后Span必须未被使用,且合并后总页数不超过最大支持页数if(nextspan->_isUse == true || nextspan->_n + span->_n > NPAGES - 1){break; // 不满足合并条件,终止向后合并}PopIdToMap(nextspan->_pageid, nextspan->_n);// 执行合并:将后Span的内存合并到当前Span中(只需更新页数,起始页ID不变)span->_n += nextspan->_n;           // 当前Span的页数 = 原页数 + 后Span的页数// 清理后Span:从其所属的SpanList桶中删除,再释放后Span对象bool b = _spanlist[nextspan->_n].erase(nextspan);if(!b){Log(LogModule::ERROR) << "nextspan erase fail!";}delete nextspan; // 释放后Span的堆内存}// -------------------------- 第三步:合并后的Span回桶 + 维护映射 --------------------------// 1. 将合并后的大Span放入对应页数的SpanList桶中(桶索引=合并后的页数)_spanlist[span->_n].PushFront(span);// 2. 删除合并后Span的全页映射(原映射可能包含合并前的页ID,需清理)//PopIdToMap(span->_pageid, span->_n);// 3. 建立合并后Span的首尾页映射(空闲Span只需维护首尾,减少哈希表开销,方便后续合并)PushIdToMap(span->_pageid, span->_n, span);          // 起始页ID映射//PushIdToMap(span->_pageid + span->_n - 1, 1, span); // 结束页ID映射}// 私有构造函数:单例模式核心,禁止外部直接实例化(确保全局仅通过GetInstance()获取实例)PageCache(){}private:static std::recursive_mutex _mutex;  // 静态互斥锁:保护单例初始化、SpanList操作、哈希表操作的线程安全// 核心数据结构1:SpanList哈希桶数组,索引=Span的页数(如索引4存储4页的空闲Span)SpanList _spanlist[NPAGES];// 核心数据结构2:页ID到Span的哈希表,用于快速通过页ID查找对应的Span(释放/合并时关键)std::unordered_map<PageID, Span*> _idtospanmap;
};// 静态成员变量定义:C++17特性,inline修饰静态成员变量可直接在头文件中定义(避免ODR冲突)
// 作用:初始化PageCache的静态互斥锁,确保全局唯一实例的锁有效
inline std::recursive_mutex PageCache::_mutex;

test测试函数

#include "Threadcache.hpp"
#include <cstddef>
#include <pthread.h>
#include <vector>// 测试次数和线程数量
const int TEST_COUNT = 1000;    // 每个线程的操作次数
const int THREAD_COUNT = 8;     // 测试线程数量// 线程函数:并发申请和释放内存
void* thread_func(void* arg) {// 初始化线程局部缓存pTLSThreadCache = new ThreadCache;if (!pTLSThreadCache) {std::cerr << "线程缓存初始化失败" << std::endl;return nullptr;}// 存储分配的内存块指针和大小std::vector<std::pair<void*, size_t>> allocations;allocations.reserve(TEST_COUNT);// 随机种子,避免所有线程申请相同大小unsigned int seed = time(nullptr) + pthread_self();// 阶段1:随机申请不同大小的内存for (int i = 0; i < TEST_COUNT; ++i) {// 申请1~1024字节的随机大小size_t size = (rand_r(&seed) % 1024) + 1;void* ptr = pTLSThreadCache->Allocate(size);if (ptr) {allocations.emplace_back(ptr, size);// 写入数据验证内存可访问*(static_cast<char*>(ptr)) = 'a' + (i % 26);} else {std::cerr << "线程" << pthread_self() << "分配内存失败,大小: " << size << std::endl;}}//Log(LogModule::DEBUG) << "-----------------释放------------------------";// 阶段2:释放所有申请的内存for (auto& [ptr, size] : allocations) {// 验证内存数据是否正确if (*(static_cast<char*>(ptr)) < 'a' || *(static_cast<char*>(ptr)) > 'z') {std::cerr << "线程" << pthread_self() << "内存数据验证失败--" << *(static_cast<char*>(ptr)) << std::endl;}pTLSThreadCache->Deallocate(ptr, size);}// 清理线程局部缓存delete pTLSThreadCache;pTLSThreadCache = nullptr;std::cout << "线程" << pthread_self() << "完成测试" << std::endl;return nullptr;
}int main() 
{pthread_t threads[THREAD_COUNT];int ret;//ENABLE_LOG_FILE_STRATEGY();std::cout << "开始多线程内存池测试,线程数: " << THREAD_COUNT << ", 每个线程操作次数: " << TEST_COUNT << std::endl;// 创建多个测试线程for (int i = 0; i < THREAD_COUNT; ++i) {ret = pthread_create(&threads[i], nullptr, thread_func, nullptr);if (ret != 0) {std::cerr << "创建线程" << i << "失败" << std::endl;return 1;}}// 等待所有线程完成for (int i = 0; i < THREAD_COUNT; ++i) {pthread_join(threads[i], nullptr);}std::cout << "所有线程测试完成!" << std::endl;return 0;
}

文章转载自:

http://y69vmq2N.sqrpb.cn
http://Bhg2CGye.sqrpb.cn
http://VtBmaAoO.sqrpb.cn
http://1RBlt5wr.sqrpb.cn
http://raSwNF1A.sqrpb.cn
http://T84IKL2a.sqrpb.cn
http://YmmRT13v.sqrpb.cn
http://rZTlKobf.sqrpb.cn
http://oocP3fCc.sqrpb.cn
http://e3OyrC3u.sqrpb.cn
http://N2wieksM.sqrpb.cn
http://p0Zrq8Jm.sqrpb.cn
http://8M7ioof3.sqrpb.cn
http://BlwKldzO.sqrpb.cn
http://abDtTfcA.sqrpb.cn
http://3JuMzQdf.sqrpb.cn
http://nqYEyEkg.sqrpb.cn
http://D43WLM5K.sqrpb.cn
http://ITB05BEy.sqrpb.cn
http://Bca709Ca.sqrpb.cn
http://nP6mK0cC.sqrpb.cn
http://8MjNgKuJ.sqrpb.cn
http://Y2ZJa7AK.sqrpb.cn
http://cfReannc.sqrpb.cn
http://7PiQclKB.sqrpb.cn
http://UOPZgmHv.sqrpb.cn
http://RGy5hAY0.sqrpb.cn
http://0KPfereU.sqrpb.cn
http://8716LBnU.sqrpb.cn
http://hIwbL4vj.sqrpb.cn
http://www.dtcms.com/a/385133.html

相关文章:

  • 数值计算2
  • 硬件 - oring多电源切换
  • RocketMQ-高性能消息中间件的原理
  • DevOps历程--GitLab安装与使用教程(Docker安装和编译安装两种方式)
  • 大屏可视化动图渲染
  • Claude Code生态、实战
  • 系统架构设计师备考第24天——需求工程
  • 逻辑回归模型:基于鸢尾花数据集的多分类任务全流程
  • 实战适配器模式
  • OpenLayers数据源集成 -- 章节十三:EsriJSON图层详解
  • RabbitMQ—运维篇
  • 一站式YOLO目标检测解决方案:训练、测试、推理与数据集转换全流程指南
  • lamp脚本部署
  • 前端构建工具有哪些?常用前端构建工具对比、前端构建工具推荐与最佳实践分析
  • Charles日志分析与流量监控技巧 高效排查接口与性能问题
  • ACM模式的输入部分——input().strip() 与 sys.stdin.readline() 的不同
  • ““.equals(studentID)
  • 【Lesson 3】CUDA 编程模型:线程、块、网格 —— Ubuntu 22.04 + RTX 30/40 系列实战
  • [Windows]C盘瘦身 --- 软件搬家
  • CLIP 完全上手指南:从安装、下载加速、文本/图像编码到图文匹配,一篇全搞定!
  • BKY莱德因:5大黑科技逆转时光
  • 开源嵌入模型推荐与选型指南
  • 科普:Python中为什么“from .utils” 不能写成 “from ./utils”?
  • 客户粘性提升策略是什么?系统化策略提升客户粘性指南
  • Spring 框架从入门到精通(第一篇)—— 框架核心与 IOC 容器实践
  • 通过DSL生成Jenkins流水线
  • 构建AI智能体:三十四、LangChain SQLDatabaseToolkit终极指南:架构、优势与最佳实践
  • 算法 --- 字符串
  • PDF 文件创建时间属性怎样批量修改详细教程
  • hutool DesensitizedUtil