【C++高并发内存池】 2.ThreadCache设计

发布时间:2026/11/3 2:54:22
【C++高并发内存池】 2.ThreadCache设计 目录前言1.Common.hpp2.UnitTest.cpp3. ConCurrentAlloc.h4. ThreadCache.h5.ThreadCache.cpp设计总结前言前面一个设计了一个定长内存池经过与传统的内存申请方式对比初步实现了一个可承受频繁申请与释放的高性能的内存池这是我们首先要看到的一个内存池被设计出来实现的可观效果。从这里开始我们将一步步的对高并发内存池进行由浅入深的设计本节我们来设计ThreadCache。threadcache线程缓存是每个线程独有的⽤于小于256KB的内存的分配线程从这里申请内存 不需要加锁每个线程独享⼀个cache这也就是这个并发线程池高效的地方1.Common.hpp我们将公共代码独立合并到这个文件当中方便调用#pragma once #include iostream #include vector #include ctime #include new #include assert.h #include thread static const size_t MAX_SIZE 256 * 1024; static const size_t NFREE_LIST 208; static void* Next(void* obj) { return *(void**)obj; } class FreeList { public: void Push(void* obj) { Next(obj) _freelist_head; _freelist_head obj; } void* Pop() { void* obj _freelist_head; _freelist_head Next(obj); return obj; } bool Empty() { return _freelist_head nullptr; } private: void* _freelist_head; }; class SizeClass { public: // 整体控制在最多10%的浪费 // [1,128] 8 byte对齐 freelist[0,16) //16个桶 // [1281,1024] 16byte 对齐 freelist[16,72) // [10241,8*1024] 128 byte对齐 freelist[72,128) // [8*10241,64*1024] 1024 byte对齐 freelist[128,184) // [64*10241,256*1024] 8*1024 byte对齐 freelist[184,208) static inline size_t _RoundUp(size_t size, size_t alignNum) { return ((size alignNum - 1) ~(alignNum - 1)); } //向上对齐 7-8 8-8 9-16 static inline size_t RoundUp(size_t size) { if (size 128) { return _RoundUp(size, 8); } else if (size 1024) { return _RoundUp(size, 16); } else if (size 8 * 1024) { return _RoundUp(size, 128); } else if (size 64 * 1024) { return _RoundUp(size, 1024); } else if (size 256 * 1024) { return _RoundUp(size, 8 * 1024); } else { assert(false); return -1; } } // 前16个桶每个桶挂一个链链上的每一块内存大小都一样往后每个桶按照8字节的步长增加 // 第一个桶的内存都是8字节第二个都是16字节...最后一个都是128字节 static inline size_t _Index(size_t bytes, size_t align_shift) { return ((bytes (1 align_shift) - 1) align_shift) - 1; } // 计划映射到哪一个自由链表桶 static inline size_t Index(size_t bytes) { //每个区间有多少个链 static int group_array[4] { 16, 56, 56, 56 }; if (bytes 128) { return _Index(bytes, 3); } else if (bytes 1024) { return _Index(bytes - 128, 4) group_array[0]; } else if (bytes 8 * 1024) { return _Index(bytes - 1024, 7) group_array[1] group_array[0]; } else if (bytes 64 * 1024) { return _Index(bytes - 8 * 1024, 10) group_array[2] group_array[1] group_array[0]; } else if (bytes 256 * 1024) { return _Index(bytes - 64 * 1024, 13) group_array[3] group_array[2] group_array[1] group_array[0]; } else { assert(false); } return -1; } private: };2.UnitTest.cpp我们从用户的角度来一步步探查当前的内存池完成进度#include ObjectPool.hpp #include ConcurrentAlloc.h void Alloc1() { for (int i 1; i 5; i) { void* ptr ConcurrentAlloc(6); } } void Alloc2() { for (int i 1; i 5; i) { void* ptr ConcurrentAlloc(7); } } void TLSTest() { std::thread t1(Alloc1); std::thread t2(Alloc2); t1.join(); t2.join(); } int main() { //TestObjectPool(); TLSTest(); return 0; }这里分别创建了两个线程来执行两个不同的Alloc函数里面调用的ConcurrentAlloc很明显是为ptr申请内存的我们来看看这个函数3. ConCurrentAlloc.h#pragma once #include ThreadCache.h #include Common.hpp static void* ConcurrentAlloc(size_t size) { // 通过TLS每个线程无锁地获取自己的专属ThreadCache对象 if (pTLSThreadCache nullptr) { //懒加载思想需要用到这个线程的时候再申请该线程的ThreadCache空间 pTLSThreadCache new ThreadCache; } // 向当前线程的ThreadCache中申请内存 return pTLSThreadCache-Allocate(size); } // 传size是要确定将不同大小的内存块精确的还给对应桶 static void ConcurrentFree(void* ptr, size_t size) { assert(pTLSThreadCache); pTLSThreadCache-Deallocate(ptr, size); }从这里就知道内存是通过类对象pTLSThreadCache来申请和释放的4. ThreadCache.h#pragma once #include Common.hpp class ThreadCache { public: void* Allocate(size_t size); void* Deallocate(void* ptr, size_t size); void* FetchFromCentralCache(size_t index, size_t alignSize); private: // 每个线程都有独自的一批存放内存的链表桶 FreeList _freelists[NFREE_LIST]; }; // TLS Thread Local Storage //线程局部存储让每一个线程都有独立的存储空间避免了加锁的开销 static _declspec(thread) ThreadCache* pTLSThreadCache nullptr;最后一句代码实现了让每一个线程都拥有一个独立的ThreadCache类它们各自管理各自内存互不干扰因此前面不同线程申请内存的时候并不需要加锁。我们来具体看看ThreadCache类中函数的实现5.ThreadCache.cpp#include ThreadCache.h void* ThreadCache::FetchFromCentralCache(size_t index, size_t alignSize) { // ... return nullptr; } void* ThreadCache::Allocate(size_t size) { assert(size MAX_SIZE); // 1. 申请字节数向上对齐 size_t alignSize SizeClass::RoundUp(size); // 2. 桶下标 size_t index SizeClass::Index(size); if (!_freelists[index].Empty()) { return _freelists[index].Pop(); } else { return FetchFromCentralCache(index, alignSize); } } void* ThreadCache::Deallocate(void* ptr, size_t size) { assert(ptr); assert(size MAX_SIZE); size_t index SizeClass::Index(size); _freelists[index].Push(ptr); }根据申请的字节数先向上对齐然后再根据字节数确定桶下标也就是从哪个桶申请如果该桶没有剩余内存了就需要向中心缓存CentralCache中申请内存但目前尚未设计到中心缓存结构因此函数FetchFromCentralCache暂不实现。回收内存机制需要对目标内存块大小进行判定确认回收到哪个桶再Push到该桶中每个桶都是存放大小一致内存块的链表该操作就是将内存块头插入链表中。设计总结给每一个线程独立申请一块ThreadCache内存每一个ThreadCache就是FreeList _freelists[NFREE_LIST]存放208个链表各个链表之间管理的内存块大小不同但是同一个链表内的所有内存块大小是一致的。用户通过不同线程调用ConcurrentAlloc和 ConcurrentFree申请和释放内存每个线程都有一个独立的ThreadCache类对象pTLSThreadCache各个线程之间申请内存互不影响因为pTLSThreadCache是线程独立资源因此不加锁也可以各自调用Allocate和Deallocate实现并发申请和释放资源提升内存申请释放效率。附加理解每个线程都有一个独立的ThreadCache对象也就是pTLSThreadCache那个但是不是一开始创建线程就申请如果不用就太浪费了而是用到了这个线程才会申请出来这里引入了“懒加载”思想来进行设计。然后根据需要申请的空间向上对齐 桶下标定位之后向该桶链表申请空间如果该桶没有空间了。再通过FetchFromCentralCache向中心缓存申请由此引入中心缓存的结构设计这个由下一篇详细介绍目前项目到这里大概就是这么个思路后续敬请期待~~

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询