电子网站建设维护,电子商务平台中搜索词拆解时,wordpress获取浏览人信息,完成网站开发需要什么样技术一、搭建PageCache1.1 数据结构与组成PageCache的数据结构与ThreadCache和CentralCache一样是一种哈希桶#xff0c;每个桶中与CentralCache类似都是一个SpanList。PageCache的每个桶中的Span管理一段以页为单位的连续内存块#xff0c;这个内存块不会被切分也就说明PageCach…一、搭建PageCache1.1 数据结构与组成PageCache的数据结构与ThreadCache和CentralCache一样是一种哈希桶每个桶中与CentralCache类似都是一个SpanList。PageCache的每个桶中的Span管理一段以页为单位的连续内存块这个内存块不会被切分也就说明PageCache中的每个Span的span_freelist都为nullptr。前面我们了解到线程缓存和中心缓存的映射关系是一样的但是PageCache的映射关系与两者都不同。PageCache的整个哈希桶以页的数量为下标通常为1~128也就是129个数组元素0号下标对应的位置没有数据。除了映射关系需要注意之外PageCache与CentralCache一样需要设置全局可见的。全局可见的 PageCache 能够保证中心缓存之间的操作一致性比如多个中心缓存释放的空闲页块可以在 PageCache 中进行合并减少内存碎片。代码实现如下//Commond.h static const size_t NPAGES 129;//PageCache.h #pragma once #includeCommond.h class PageCache { public: static inline PageCache* GetInstance() { return _sInst; } Span* NewSpan(size_t pagenum); std::mutex _pageMtx; private: SpanList SpanLists[NPAGES]; PageCache() {} PageCache(const PageCache) delete; static PageCache _sInst; };1.2 NewSpan在高并发内存池的 PageCache 中NewSpan接口是用于获取或创建一个新的Span以满足上层缓存如中心缓存对内存页块的需求其主要功能包括以下几个方面当上层缓存如中心缓存请求内存页块时NewSpan接口会先在 PageCache 中按页块大小页数分类管理的哈希桶中查找。比如中心缓存请求一个 8 页的SpanNewSpan会查找SpanLists中下标为8的元素也就是对应 8 页的SpanList看是否有空闲的Span可供分配。如果在对应链表中找到空闲Span则直接将其分配给请求方并从链表中移除该Span同时更新相关元数据。如果没有找到接口会尝试继续向下寻找管理页数更大的Span并将其切分出需要的页数大小并调整Span中的元数据将切分出来的符合需求的内存空间交给新的Span进行管理然后将新Span返回出去将原先的Span调整链入的位置确保管理的内存空间与下标相符。如果第一次在对应链表中没有找到空闲Span继续向下寻找管理页数更大的Span也没有找到此时就会向操作系统申请新的连续物理内存页。例如通过系统调用mmap在 Linux 系统中或VirtualAlloc在 Windows 系统中来分配所需数量的物理页。//PageCache.cpp Span* PageCache::NewSpan(size_t pagenum) { assert(pagenum0pagenumNPAGES); //首先查看对应下标处有没有Span if (!SpanLists[pagenum].Empty()) { return SpanLists[pagenum].PopFront(); } for (size_t i pagenum 1; i NPAGES; i) { if (!SpanLists[i].Empty()) { Span* nSpan SpanLists[i].PopFront(); Span* kSpan new Span; kSpan-Page_Id nSpan-Page_Id; kSpan-_n pagenum; nSpan-Page_Id pagenum; nSpan-_n - pagenum; SpanLists[nSpan-_n].PushFront(nSpan); return kSpan; } } //表示后续整个哈希桶都为空则向堆申请内存 Span* bigspannew Span; void* ptr SystemAlloc(NPAGES-1); bigspan-Page_Id (PAGE_ID)ptr PAGE_SHIFT; bigspan-_n NPAGES - 1; SpanLists[bigspan-_n].PushFront(bigspan); return NewSpan(pagenum); }这里因为向系统申请内存空间有两种调用接口情况mmap在 Linux 系统中或VirtualAlloc在 Windows 系统中所以我们使用条件编译来解决//Commond.h static inline void* SystemAlloc(size_t pagenum) { #ifdef _WIN32 void* ptr VirtualAlloc(0, pagenum PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); #else // linux下brk mmap等 #endif if (ptr nullptr) throw std::bad_alloc(); return ptr; }在这里VirtualAlloc返回的起始地址是页对齐的也就是说起始地址是页大小的整数倍不会指向页中间然后将这连续的 8 个页的虚拟地址空间分配给调用者。比如申请 8 个页假设页大小为 4KB 即 32KB 它会在进程的虚拟地址空间中查找一块满足条件的空闲区域。这个区域的起始地址是页对齐的所以这段代码void* ptr VirtualAlloc(0, pagenum PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);中ptr指向的地址一定可以整除一个页的大小所以此时我们就知道了这段内存空间起始页的地址当我们通过bigspan来描述管理这段内存空间确定起始页的页号时只需要将ptr除以一个页的大小就解决了bigspan-Page_Id (PAGE_ID)ptr PAGE_SHIFT;但是这里需要注意的是ptr是一个指针变量用于存储内存地址不支持直接进行除运算。所以我们必须要将其中的地址值转化为数值我们知道在64位机器下指针大小为8字节32位机器下为4字节这就意味这当我们对ptr进行类型转化时要保证转化后的类型是具有足够的空间存储地址值的否则就可能发生截断导致数据丢失。比如在64位环境中将ptr(8字节)类型转化为size_t(4字节)虽然将地址数值化后可以进行除运算了但是发生了截断数据丢失了在32位环境中将ptr(4字节)类型转化为longlong(8字节)编译器会将 32 位的指针值零扩展对于无符号指针或符号扩展对于有符号解释到 64 位填充高位的 4 字节。但是当我们进行反向转化会有以下情况若将转换后的long long再转回 32 位指针只要原始地址未超过 32 位范围即高 4 字节为 0不会出错。但如果long long的值超过 32 位如高 4 字节非 0转回 32 位指针时会截断高位导致地址错误。所以在项目中为了避免类型转化不匹配发生的指针值截断或冗余我们一般通过条件编译使得在64位环境中将指针类型转化为longlong8字节在32位环境中转化为size_t4字节//Commond.h #ifdef _WIN64 typedef unsigned long long PAGE_ID; #elif _WIN32 typedef size_t PAGE_ID; #else // linux #endif引入条件编译后后续只需要将ptr转化为PAGE_ID就可以了此时编译器会自动匹配相应类型。最后我们将描述128页连续内存块的Span链入下标128处然后通过递归调用进行切分后将所需内存块分配给上层1.3 GetOneSpanGetOneSpan接口的功能是在一个桶中的SpanList中获取一个span_freelist不为空也就是存在待分配内存块的Span对象。如果遇到下面的两种情况SpanList为空里面没有SpanSpanList不为空但是其中所有Span的span_freelist为空说明没有空闲的Span可供分配这时就需要向PageCache中申请一段连续的内存空间并用Span描述起来后切分成对应大小的内存块链入到span_freelist中后再返回给CentralCache以便于向上层ThreadCache分配。Spen* CentralCache::GetOneSpen(SpenList it, size_t alignsize) { it._mtx.lock(); //首先遍历对应下标下的spenlist中是否有非空spen Spen* startit.begin(); Spen* end it.end(); while (start-next!end) { if (start-_freelist ! nullptr) { //找到了非空的Spen return start; } start start-next; } //到这里说明对应下标处的spenlist中没有非空spen //找下层PageCache分配内存 //与threadcache找下层centralcache一样1.获取单例2.调用相关接口 //头插 }在PageCache中NewSpan接口的的作用是获取或创建一个新的span页块管理连续的物理内存页 以满足上层缓存如中心缓存对内存页块的需求。在这里我们直接获取PageCache的单例并调用NewSpan接口获取一个单例对象前面我们说过PageCache的每个桶中的Span管理一段以页为单位的连续内存块这个内存块不会被切分。所以通过NewSpan接口获得一个Span对象后我们还需要对其描述管理的大块内存切分成相应大小的内存块挂到span_freelist下。最后将完整的Span链入到对应桶中的SpanList中。代码实现如下Span* CentralCache::GetOneSpan(SpanList it,size_t size) { Span* start it.Begin(); Span* end it.end(); while (start ! end) { if (start-span_freelist ! nullptr) { return start; } start start-next; } it._mtx.unlock(); //走到这里说明没有空闲的Span了只能找PageCache要 PageCache::GetInstance()-_pageMtx.lock(); Span* newspan PageCache::GetInstance()-NewSpan(SizeClass::NumMovePage(size)); PageCache::GetInstance()-_pageMtx.unlock(); //将newpan中管理的大块内存进行切分后挂载其中的自由链表下 char* _start (char*)(newspan-Page_Id PAGE_SHIFT); char* _end _start (newspan-_n PAGE_SHIFT); newspan-span_freelist _start; _start size; void* tail newspan-span_freelist; while (_start_end) { NextObj(tail) _start; tail _start; _start size; } it._mtx.lock(); it.PushFront(newspan); return newspan; }这里我们需要注意的是NewSpan的参数是页的数目pagenum。我们需要根据单个对象确定需要的页的数目后才会传给NewSpan如何确定呢在CentralCache的实现中当ThreadCache没有内存块时CentralCache会给其补充一批相同大小的内存块而不是一个这里一批的数目的取决于对象大小对象太大就最多给2个太小的话最多会给512个static inline size_t NumMoveSize(size_t size) { assert(size MAXBYTES); size_t batchnum MAXBYTES / size; if (batchnum 512) { batchnum 512; } else if (batchnum2) { batchnum 2; } return batchnum; }如果我们事先确定要给ThreadCache一批512个对象的话要是CentralCache一个也分配不出来时此时就意味着PageCache分配给CentralCache的连续内存块必须大于等于512*单个对象的大小。所以需要保证newspan管理的连续内存空间必须足够CentralCache分配NumMovePage的实现如下//Commond.h中的SizeClass类中定义 static inline size_t NumMovePage(size_t size) { assert(size 0 size MAXBYTES); size_t num NumMoveSize(size); size_t pagenum num * size; pagenumpagenum PAGE_SHIFT; if (pagenum 0) { pagenum 1; } return pagenum; }在CentralCache中当我们通过GetOneSpan没有找到空闲的Span后需要调用NewSpan进入PageCache时可以将桶锁暂时解掉当我们拿到Span后要链入桶中时可以再将桶锁加上。此时其他执行流进入CentralCache会面临以下情况当我们进入PageCache解除桶锁后其他执行流进入CentralCache时也无法找到空闲的Span也会进入PageCache但此时PageCache已经被加锁执行流会陷入等待。当我们拿到Span从PageCache出来时会解PageCache的锁之前等待的执行流会进入PageCache。但是链入CentralCache时又会加上桶锁防止新加入的Span被其他执行流利用。当我们执行这段代码时//将newpan中管理的大块内存进行切分后挂载其中的自由链表下 char* _start (char*)(newspan-Page_Id PAGE_SHIFT); char* _end _start (newspan-_n PAGE_SHIFT); newspan-span_freelist _start; _start size; void* tail newspan-span_freelist; while (_start_end) { NextObj(tail) _start; tail _start; _start size; }此时通过NewSpan获取的newspan不属于任何一个数据结构其只有本执行流可见其他执行流无法通过任何方式访问到newspan所以对newspan进行切分时不需要加任何锁。同时这里还涉及到了将起始页的页号转化为起始地址的方法前面我们讲过确定起始页的页号时只需要将ptr起始地址除以一个页的大小就解决了反过来页号转化为起始地址只需要乘以一个页的大小就解决了为了方便后续指针移动进行切分我们将得到的起始地址还应该转化为char*。1.4 补充与说明如今我们的代码中还存在一些不足比如ThreadCache没有内存块向CentralCache申请补充时CentralCache会给ThreadCache很多内存块比如申请8字节内存块不足后会直接补充512个8字节内存块。这就导致一个问题用户要是之后对8字节内存块的需求越来越少时剩余的内存块就会一直存在于ThreadCache导致该部分内存空间一直无法进行合并和再分配这不符合我们的设计目的。为了避免这种情况我们引入一种慢增长的机制首先我们在ThreadCache中的每个桶中的FreeList添加一个变量_maxsize并初始化为0。并提供一个接口MaxSize用来获取_maxsize。这里要注意MaxSize要传引用返回以便于我们在类外对变量随时可以修改在ThreadCache.cpp的FetchFromCentralCache函数中当我们通过NumMoveSize算出需要一次性分配给ThreadCache的内存块数量后并不能第一时间赋值给batchnum而是首先与MaxSize的返回值也就是_maxsize比大小batchnum应该是两者的较小值。如果较小值为_maxsize时就将其赋值给batchnum并将_maxnum。这样就保证了每次分配时都比上次多分配一个内存块但是上限不会超过NumMoveSize的返回值。代码实现//向中心缓存申请空间 //并不是一块一块给内存而是根据慢开始算法给予多块内存防止重复申请提高效率 size_t batchnum min(SizeClass::NumMoveSize(size),FreeLists[index].MaxSize()); if (batchnum FreeLists[index].MaxSize()) { FreeLists[index].MaxSize(); }二、申请内存过程联调到这里我们的高并发内存池的申请内存的流程的相关接口和结构就都实现完了这一小节我们来测试一下首先我们先来测试一下单执行流也就是单线程创建一个text.cpp文件并写入以下代码#includeCommond.h #includeCentralCache.h #includeThreadCache.h #includePageCache.h #includeConcurrnetAlloc.h #includeiostream #includethread using namespace std; void Text1() { void * ptr1ConcurrentAlloc(4); void* ptr2 ConcurrentAlloc(4); void* ptr3 ConcurrentAlloc(4); void* ptr4 ConcurrentAlloc(4); void* ptr5 ConcurrentAlloc(4); void* ptr6 ConcurrentAlloc(4); std::cout ptr1 std::endl; std::cout ptr2 std::endl; std::cout ptr3 std::endl; std::cout ptr4 std::endl; std::cout ptr5 std::endl; std::cout ptr6 std::endl; } int main() { Text1(); return 0; }这是一个链接报错主要原因是我们在使用单例模式设计CentralCache和PageCache时使用一个静态成员变量static PageCache _sInst;来提供唯一的实例但是在 C 中类的静态成员变量必须在类外进行定义才能完成实例化分配内存这是由 C 的语法规则决定的。所以我们必须在CentralCache和PageCache的源文件中定义_sInst为其分配内存否则GetInstance无法获取实例对象导致报错。运行结果接着我们测试一下多执行流也就是多线程在text.cpp中引入以下测试代码void Text1() { void * ptr1ConcurrentAlloc(4); void* ptr2 ConcurrentAlloc(4); void* ptr3 ConcurrentAlloc(4); void* ptr4 ConcurrentAlloc(4); void* ptr5 ConcurrentAlloc(4); void* ptr6 ConcurrentAlloc(4); std::cout ptr1 std::endl; std::cout ptr2 std::endl; std::cout ptr3 std::endl; std::cout ptr4 std::endl; std::cout ptr5 std::endl; std::cout ptr6 std::endl; } void TextThread1() { void* ptr1 ConcurrentAlloc(4); void* ptr2 ConcurrentAlloc(4); void* ptr3 ConcurrentAlloc(4); std::cout ptr1 std::endl; std::cout ptr2 std::endl; std::cout ptr3 std::endl; } void TextThread2() { void* ptr1 ConcurrentAlloc(12); void* ptr2 ConcurrentAlloc(12); void* ptr3 ConcurrentAlloc(12); std::cout ptr1 std::endl; std::cout ptr2 std::endl; std::cout ptr3 std::endl; } void TextThread3() { void* ptr1 ConcurrentAlloc(20); void* ptr2 ConcurrentAlloc(20); void* ptr3 ConcurrentAlloc(20); std::cout ptr1 std::endl; std::cout ptr2 std::endl; std::cout ptr3 std::endl; } int main() { //Text1(); std::thread t1(TextThread1); std::thread t2(TextThread2); std::thread t3(TextThread3); t1.join(); t2.join(); t3.join(); return 0; }这段代码出现在CentralCache.cpp的GetOneSpan接口中通过监视发现指针出现了越界访问的情况我们在t3线程中每次申请20字节的空间通过对齐后为24字节。当我们第一次申请24字节内存块的时候ThreadCache对应的桶中没有内存块会向CentralCache要求补充CentralCache也没有内存块此时会向PageCache获取一个Span。因为我们申请的内存块都比较小所以结果NumMovePage的计算NewSpan只会申请1页的内存空间也就是4096字节给我们返回拿到newspan后会对这1页内存空间进行切分但是这里有个问题4096无法被24整除我们的代码中关于切分的片段是newspan-span_freelist _start; _start size; void* tail newspan-span_freelist; while (_start!_end) { NextObj(tail) _start; tail _start; _start size; }其中的while循环条件是_start!_end所以当最后一次切分时虽然不够24字节但是_start还是移动了24字节导致了指针越界。解决方法是将_start!_end改为_start_end。三、本次源码Commond.h#pragma once #includeiostream #includeassert.h #includemutex #include algorithm #include windows.h #includeCommond.h using namespace std; static const size_t MAXBYTES 256 * 1024; static const size_t NFREE_LIST 208; static const size_t NPAGES 129; static const size_t PAGE_SHIFT 12; #ifdef _WIN64 typedef unsigned long long PAGE_ID; #elif _WIN32 typedef size_t PAGE_ID; #else // linux #endif static inline void* SystemAlloc(size_t pagenum) { #ifdef _WIN32 void* ptr VirtualAlloc(0, pagenum PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); #else // linux下brk mmap等 #endif if (ptr nullptr) throw std::bad_alloc(); return ptr; } static inline void* NextObj(void* obj) { return *(void**)obj; } class FreeList { public: void Push(void* obj) { //头插 //*(void**)obj _freelist; NextObj(obj) _freelist; _freelist obj; } void* Pop() { void* obj _freelist; _freelist NextObj(_freelist); return obj; } bool Empty() { return _freelist nullptr; } void PushRange(void* start,void* end) { NextObj(end) _freelist; _freelist start; } size_t MaxSize() { return _maxsize; } private: void* _freelist nullptr; size_t _maxsize 1; }; //创建一个工具类 class SizeClass { public: 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) { assert(bytes MAXBYTES); static int group_array[] { 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[0] group_array[1]; } else if (bytes 64 * 1024) { return _Index(bytes - 8 * 1024, 10) group_array[0] group_array[1] group_array[2]; } else if (bytes 256 * 1024) { return _Index(bytes - 64 * 1024, 13) group_array[0] group_array[1] group_array[2] group_array[3]; } else { assert(false); return -1; } } /*static inline size_t roundup(size_t size, size_t align) { size_t alignsize; if (size % align ! 0) { alignsize (size / (align 1)) * align; } else { alignsize size; } return alignsize; }*/ static inline size_t roundup(size_t size,size_t align) { return (size align - 1) ~(align - 1); } static inline size_t _RoundUp(size_t size) { assert(size MAXBYTES); //1-128 if (size 0 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; } } static inline size_t NumMoveSize(size_t size) { assert(size MAXBYTES); size_t batchnum MAXBYTES / size; if (batchnum 512) { batchnum 512; } else if (batchnum2) { batchnum 2; } return batchnum; } static inline size_t NumMovePage(size_t size) { assert(size 0 size MAXBYTES); size_t num NumMoveSize(size); size_t pagenum num * size; pagenumpagenum PAGE_SHIFT; if (pagenum 0) { pagenum 1; } return pagenum; } private: }; class Span { public: size_t Page_Id 0; size_t _n 0; Span* next nullptr; Span* prev nullptr; size_t usecount 0; void* span_freelistnullptr; }; class SpanList { public: SpanList() { _head new Span; _head-next _head; _head-prev _head; } bool Empty() { return _head-next _head; } Span* Begin() { return _head-next; } Span* end() { return _head; } Span* PopFront() { assert(_head-next ! _head); Span* pos _head-next; _head-next pos-next; pos-next-prev _head; return pos; } void PushFront(Span* pos) { pos-next _head-next; pos-prev _head; _head-next-prev pos; _head-next pos; } std::mutex _mtx; // 桶锁 private: Span* _head nullptr; };ThreadCache.h#pragma once #includeCommond.h class ThreadCache { public: //申请内存 void* Allocate(size_t size); //释放内存 void Deallocate(void* ptr, size_t size); //向CentralCache要内存块 void* FetchFromCentralCache(size_t index,size_t size); private: FreeList FreeLists[NFREE_LIST]; }; static _declspec(thread) ThreadCache* pTLSThreadCache nullptr;ThreadCache.cpp#includeThreadCache.h #includeCentralCache.h void* ThreadCache::Allocate(size_t size) { assert(size MAXBYTES); //根据对齐原则确定对齐数 size_t alignsize SizeClass::_RoundUp(size); size_t index SizeClass::Index(size); assert(index NFREE_LIST); //确定对应下标的桶里面有没有空闲内存块 if (!FreeLists[index].Empty()) { //Pop一个 return FreeLists[index].Pop(); } else { //向下层CentralCache要内存块 return FetchFromCentralCache(index,alignsize); } } void ThreadCache::Deallocate(void* ptr, size_t size) { assert(size MAXBYTES); size_t index SizeClass::Index(size); FreeLists[index].Push(ptr); } void* ThreadCache::FetchFromCentralCache(size_t index,size_t size) { size_t batchnum min(SizeClass::NumMoveSize(size),FreeLists[index].MaxSize()); if (batchnum FreeLists[index].MaxSize()) { FreeLists[index].MaxSize(); } void* start nullptr; void* end nullptr; size_t actualnum CentralCache::GetInstance()-FetchRangeObj(start,end,batchnum,size); if(actualnum1) { FreeLists[index].PushRange(NextObj(start), end); } return start; }CentralCache.h#pragma once #includeCommond.h class CentralCache { public: static inline CentralCache* GetInstance() { return _sInst; } size_t FetchRangeObj(void* start, void* end, size_t batchNum, size_t index); Span* GetOneSpan(SpanList it,size_t size); private: SpanList SpanLists[NFREE_LIST]; CentralCache() {} CentralCache(const CentralCache ch) delete; static CentralCache _sInst; };CentralCache.cpp#includeCentralCache.h #includePageCache.h #includeCommond.h CentralCache CentralCache::_sInst; size_t CentralCache::FetchRangeObj(void* start, void* end, size_t batchNum, size_t size) { size_t index SizeClass::Index(size); SpanLists[index]._mtx.lock(); Span* newspen GetOneSpan(SpanLists[index],size); size_t actualnum 1; start newspen-span_freelist; end start; while (NextObj(end) ! nullptr actualnum batchNum) { end NextObj(end); actualnum; } newspen-usecount actualnum; //对newspen的自由链表进行裁切 newspen-span_freelist NextObj(end); NextObj(end) nullptr; SpanLists[index]._mtx.unlock(); return actualnum; } Span* CentralCache::GetOneSpan(SpanList it,size_t size) { Span* start it.Begin(); Span* end it.end(); while (start ! end) { if (start-span_freelist ! nullptr) { return start; } start start-next; } it._mtx.unlock(); //走到这里说明没有空闲的Span了只能找PageCache要 PageCache::GetInstance()-_pageMtx.lock(); Span* newspan PageCache::GetInstance()-NewSpan(SizeClass::NumMovePage(size)); PageCache::GetInstance()-_pageMtx.unlock(); //将newpan中管理的大块内存进行切分后挂载其中的自由链表下 char* _start (char*)(newspan-Page_Id PAGE_SHIFT); size_t bytes newspan-_n PAGE_SHIFT; char* _end _start bytes; newspan-span_freelist _start; _start size; void* tail newspan-span_freelist; while (_start_end) { NextObj(tail) _start; tail _start; _start size; } it._mtx.lock(); it.PushFront(newspan); return newspan; }PageCache.h#pragma once #includeCommond.h class PageCache { public: static inline PageCache* GetInstance() { return _sInst; } Span* NewSpan(size_t pagenum); std::mutex _pageMtx; private: SpanList SpanLists[NPAGES]; PageCache() {} PageCache(const PageCache) delete; static PageCache _sInst; };PageCache.cpp#includePageCache.h PageCache PageCache::_sInst; Span* PageCache::NewSpan(size_t pagenum) { assert(pagenum0pagenumNPAGES); //首先查看对应下标处有没有Span if (!SpanLists[pagenum].Empty()) { return SpanLists[pagenum].PopFront(); } for (size_t i pagenum 1; i NPAGES; i) { if (!SpanLists[i].Empty()) { Span* nSpan SpanLists[i].PopFront(); Span* kSpan new Span; kSpan-Page_Id nSpan-Page_Id; kSpan-_n pagenum; nSpan-Page_Id pagenum; nSpan-_n - pagenum; SpanLists[nSpan-_n].PushFront(nSpan); return kSpan; } } //表示后续整个哈希桶都为空则向堆申请内存 Span* bigspannew Span; void* ptr SystemAlloc(NPAGES-1); bigspan-Page_Id (PAGE_ID)ptr PAGE_SHIFT; bigspan-_n NPAGES - 1; SpanLists[bigspan-_n].PushFront(bigspan); return NewSpan(pagenum); }ConcurrnetAlloc.h#pragma once #includeCommond.h #includeThreadCache.h static void* ConcurrentAlloc(size_t size) { // 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象 if (pTLSThreadCache nullptr) { pTLSThreadCache new ThreadCache; } return pTLSThreadCache-Allocate(size); } static void ConcurrentFree(void* ptr, size_t size) { assert(pTLSThreadCache); pTLSThreadCache-Deallocate(ptr, size); }