资讯详情

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

📅 2026/11/3 2:54:15 | 华诺云谱 👁 阅读
【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向中心缓存申请由此引入中心缓存的结构设计这个由下一篇详细介绍目前项目到这里大概就是这么个思路后续敬请期待~~
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑