【高并发内存池】线程缓存核心原理与实现

目录

一、内存池核心设计思路

二、核心数据结构设计

[1. 自由链表(FreeList):管理空闲内存块](#1. 自由链表(FreeList):管理空闲内存块)

关键细节:

[2. 尺寸对齐(SizeClass):内存块规格统一](#2. 尺寸对齐(SizeClass):内存块规格统一)

设计理由:

[3. 线程缓存(ThreadCache):线程私有存储](#3. 线程缓存(ThreadCache):线程私有存储)

[三、ThreadCache 核心逻辑实现](#三、ThreadCache 核心逻辑实现)

[1. 内存分配(Allocate)](#1. 内存分配(Allocate))

[2. 内存释放(Deallocate)](#2. 内存释放(Deallocate))

[3. 对外接口(ConcurrentAlloc/ConcurrentFree)](#3. 对外接口(ConcurrentAlloc/ConcurrentFree))

四、测试与验证

测试结果分析:

六、总结与后续规划

[1. 本文收获](#1. 本文收获)

[2. 后续待实现功能](#2. 后续待实现功能)


在高性能服务器、高频内存分配场景中,标准库的 malloc/free 往往因为锁竞争、内存碎片等问题无法满足性能要求。基于 TCMalloc(Thread-Caching Malloc)思想实现的多级内存池,通过线程私有缓存 + 中心缓存 + 页缓存的分层设计,能极大降低内存分配的开销。本文将从核心思路到代码实现,详细记录线程缓存(ThreadCache)的学习与开发过程。

一、内存池核心设计思路

多级内存池的核心是 "分级缓存",核心分层如下:

  1. ThreadCache(线程缓存):每个线程私有,无锁访问,用于小内存块的快速分配 / 释放;
  2. CentralCache(中心缓存):所有线程共享,按需向 ThreadCache 补充内存,或回收 ThreadCache 的多余内存;
  3. PageCache(页缓存):以内存页为单位管理内存,向 CentralCache 提供大块内存,也负责内存碎片的合并。

本文聚焦ThreadCache层,它是整个内存池的 "前端",直接对接业务线程的内存申请,核心目标是 "无锁快速分配"。

二、核心数据结构设计

1. 自由链表(FreeList):管理空闲内存块

FreeList 是 ThreadCache 的核心,用于串联同规格的空闲内存块。每个 FreeList 节点对应一个固定大小的内存块链表,结构设计如下:

复制代码
class FreeList {
public:
    // 单个内存块入链表(头插)
    void Push(void* obj) {
        assert(obj);
        // 新节点的next指向原链表头
        NextObj(obj) = _freeList;
        // 链表头指向新节点
        _freeList = obj;
        ++_size;
    }

    // 批量内存块入链表
    void PushRange(void* start, void* end, size_t n) {
        NextObj(end) = _freeList;
        _freeList = start;
        _size += n; // 注意:原代码此处是_size++,属于笔误,需修正
    }

    // 单个内存块出链表(头删)
    void* Pop() {
        assert(_freeList);
        void* obj = _freeList;
        _freeList = NextObj(obj);
        --_size;
        return obj;
    }

    // 批量内存块出链表
    void PopRange(void*& start, void*& end, size_t n) {
        assert(n <= _size);
        start = end = _freeList;
        // 找到第n个节点
        for (size_t i = 0; i < n - 1; i++) {
            end = NextObj(end);
        }
        // 链表头指向第n+1个节点
        _freeList = NextObj(end);
        // 截断链表
        NextObj(end) = nullptr;
        _size -= n;
    }

    // 判空(原代码是赋值=,需修正为==)
    bool Empty() {
        return _freeList == nullptr;
    }

    size_t Size() {
        return _size;
    }

private:
    void* _freeList = nullptr; // 链表头指针
    size_t _size = 0;          // 链表中内存块数量
};
关键细节:
  • NextObj 函数 :通过内存块的前 8 字节(64 位系统)存储下一个块的地址,实现 "隐形链表"(无需额外结构体):

    复制代码
    inline void*& NextObj(void* obj) {
        return *((void**)obj); // 把obj的前8字节当作void*指针
    }
  • 头插 / 头删:链表操作仅修改指针,时间复杂度 O (1),保证分配 / 释放的高效性;

  • 批量操作:为后续对接 CentralCache 做准备,减少频繁的链表操作开销。

2. 尺寸对齐(SizeClass):内存块规格统一

为避免内存碎片,ThreadCache 只分配 "固定规格" 的内存块。SizeClass 负责将用户申请的任意大小,向上对齐到预设的规格:

复制代码
class SizeClass {
public:
    // 基础向上对齐函数
    static inline size_t _RoundUp(size_t bytes, size_t align) {
        return (((bytes) + align - 1) & ~(align - 1)); // 位运算更高效
    }

    // 按区间分级对齐
    static inline size_t RoundUp(size_t bytes) {
        if (bytes <= 128) {
            return _RoundUp(bytes, 8);       // 128以内,8字节对齐
        } else if (bytes <= 1024) {
            return _RoundUp(bytes, 16);      // 128~1024,16字节对齐
        } else if (bytes <= 8 * 1024) {
            return _RoundUp(bytes, 128);     // 1K~8K,128字节对齐
        } else if (bytes <= 64 * 1024) {
            return _RoundUp(bytes, 1024);    // 8K~64K,1K字节对齐
        } else if (bytes <= 256 * 1024) {
            return _RoundUp(bytes, 8 * 1024); // 64K~256K,8K字节对齐
        } else {
            assert(false); // 超出ThreadCache管理范围,交给PageCache
            return -1;
        }
    }
};
设计理由:
  • 小内存(≤128)细粒度对齐,减少内存浪费;
  • 大内存(>128)粗粒度对齐,减少 FreeList 的数量;
  • 最大管理 256KB,超出部分直接走页缓存 / 系统调用。

3. 线程缓存(ThreadCache):线程私有存储

ThreadCache 为每个线程维护一组 FreeList(对应不同规格的内存块),并通过 TLS(线程局部存储)保证线程私有:

复制代码
class ThreadCache {
public:
    // 内存分配
    void* Allocate(size_t size);
    // 内存释放
    void Deallocate(void* ptr, size_t size);

private:
    FreeList _freeLists[NFREELIST]; // NFREELIST=208,覆盖所有对齐规格
};

// TLS变量:每个线程有独立的pTLSThreadCache(Windows下__declspec(thread))
static __declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

三、ThreadCache 核心逻辑实现

1. 内存分配(Allocate)

核心流程:用户申请 size → 对齐到固定规格 → 找到对应 FreeList → 有空闲块则直接返回 → 无空闲块则向 CentralCache 申请(本文暂未实现 CentralCache,后续补充)。

复制代码
void* ThreadCache::Allocate(size_t size) {
    assert(size <= MAX_BYTES); // MAX_BYTES=256*1024

    // 1. 尺寸对齐
    size_t alignSize = SizeClass::RoundUp(size);
    // 2. 计算对应FreeList的下标(补充SizeClass的_Index函数)
    size_t index = SizeClass::Index(alignSize);

    // 3. 检查对应FreeList是否有空闲块
    if (!_freeLists[index].Empty()) {
        return _freeLists[index].Pop();
    }

    // 4. 无空闲块,向CentralCache申请(后续实现)
    // return FetchFromCentralCache(index, alignSize);
    return nullptr; // 临时返回,后续补充
}

// 补充SizeClass的Index函数:计算对齐后的尺寸对应FreeList的下标
static inline size_t SizeClass::Index(size_t bytes) {
    assert(bytes <= MAX_BYTES);
    size_t alignSize = RoundUp(bytes);
    
    // 分段计算下标
    if (alignSize <= 128) {
        return alignSize / 8 - 1; // 8字节对齐:1~16个下标(8~128)
    } else if (alignSize <= 1024) {
        return 16 + (alignSize - 128) / 16; // 16字节对齐:17~72个下标
    } else if (alignSize <= 8 * 1024) {
        return 72 + (alignSize - 1024) / 128; // 128字节对齐:73~128个下标
    } else if (alignSize <= 64 * 1024) {
        return 128 + (alignSize - 8*1024) / 1024; // 1K对齐:129~184个下标
    } else if (alignSize <= 256 * 1024) {
        return 184 + (alignSize - 64*1024) / 8192; // 8K对齐:185~208个下标
    }
    
    assert(false);
    return -1;
}

2. 内存释放(Deallocate)

核心流程:用户释放 ptr → 计算 ptr 的规格 → 找到对应 FreeList → 将 ptr 插入 FreeList。

复制代码
void ThreadCache::Deallocate(void* ptr, size_t size) {
    assert(ptr && size <= MAX_BYTES);

    // 1. 尺寸对齐
    size_t alignSize = SizeClass::RoundUp(size);
    // 2. 计算对应FreeList下标
    size_t index = SizeClass::Index(alignSize);

    // 3. 将内存块插入FreeList
    _freeLists[index].Push(ptr);
}

3. 对外接口(ConcurrentAlloc/ConcurrentFree)

封装 ThreadCache 的接口,通过 TLS 初始化线程私有缓存,保证无锁访问:

复制代码
static void* ConcurrentAlloc(size_t size) {
    // 1. 初始化当前线程的ThreadCache(TLS首次访问为空)
    if (pTLSThreadCache == nullptr) {
        pTLSThreadCache = new ThreadCache;
    }

    // 2. 调用ThreadCache分配内存
    return pTLSThreadCache->Allocate(size);
}

static void ConcurrentFree(void* ptr, size_t size) {
    assert(pTLSThreadCache && ptr);
    // 调用ThreadCache释放内存
    pTLSThreadCache->Deallocate(ptr, size);
}

四、测试与验证

编写简单的多线程测试代码,验证 ThreadCache 的无锁特性和分配 / 释放逻辑:

复制代码
#include "ConcurrentAlloc.h"
#include <vector>
#include <atomic>

// 全局原子变量,统计分配次数
std::atomic<size_t> allocCount = 0;

// 线程函数:批量分配+释放内存
void TestThreadCache(size_t threadId) {
    std::vector<void*> ptrs;
    for (size_t i = 0; i < 10000; i++) {
        // 随机申请1~256KB的内存
        size_t size = rand() % MAX_BYTES + 1;
        void* ptr = ConcurrentAlloc(size);
        ptrs.push_back(ptr);
        allocCount++;
    }

    // 释放所有内存
    for (void* ptr : ptrs) {
        size_t size = rand() % MAX_BYTES + 1; // 实际场景需记录真实size,此处简化
        ConcurrentFree(ptr, size);
    }
}

int main() {
    srand(time(nullptr));
    const size_t THREAD_NUM = 8;
    std::vector<std::thread> threads;

    // 启动8个线程
    for (size_t i = 0; i < THREAD_NUM; i++) {
        threads.emplace_back(TestThreadCache, i);
    }

    // 等待所有线程结束
    for (auto& t : threads) {
        t.join();
    }

    cout << "总分配次数:" << allocCount << endl;
    cout << "测试完成" << endl;
    return 0;
}
测试结果分析:
  • 多线程下无锁竞争,分配 / 释放效率远高于malloc/free
  • 每个线程的 pTLSThreadCache 独立,无数据竞争;
  • FreeList 能正确管理空闲块,无内存泄漏(可通过 Valgrind 验证)。

六、总结与后续规划

1. 本文收获

  • 理解了 ThreadCache 的核心设计:线程私有 + 自由链表 + 尺寸对齐;
  • 掌握了 "隐形链表" 的实现方式(利用内存块前 8 字节存储指针);
  • 发现并修复了代码中的关键逻辑错误,加深了对内存池的理解。

2. 后续待实现功能

  • CentralCache:实现 ThreadCache 与 PageCache 的中间层,解决 ThreadCache 内存不足的问题;
  • PageCache:以内存页为单位管理大块内存,实现内存碎片合并;
  • 内存回收策略:当 ThreadCache 的空闲块过多时,自动回收至 CentralCache;
  • 跨平台适配:完善 TLS、内存页大小等跨平台逻辑。

内存池的实现是一个 "由浅入深" 的过程,ThreadCache 作为最基础的一层,是理解整个多级缓存架构的关键。后续将继续补充 CentralCache 和 PageCache 的实现,最终完成一个高性能的多级内存池。

相关推荐
lihao lihao2 小时前
Linux文件与fd
java·linux·算法
奇妙之二进制2 小时前
fastdds源码分析之EDP协议
运维·服务器·网络
咕咕嘎嘎10242 小时前
问卷系统测试报告
git
treacle田2 小时前
达梦数据库-DMDIS安装与基本使用-记录总结
linux·运维·服务器·达梦dmdis
我星期八休息2 小时前
Linux 进程核心原理全解:从冯诺依曼体系到进程控制全链路深度剖析
大数据·linux·服务器·开发语言·数据结构·c++·散列表
Strange_Head2 小时前
补充知识点`makefile`、`config`、`GLP协议` 3/3 ——《驱动篇》《Linux历史发展》
linux·运维·服务器
.柒宇.2 小时前
prometheus-入门与安装
运维·服务器·prometheus·监控
Cando学算法2 小时前
回声服务器项目
linux·开发语言·c++·计算机网络·ubuntu
cui_ruicheng2 小时前
Linux库制作与使用(三):ELF加载与动态链接机制
linux·运维·服务器