高并发内存池(4)——实现CentralCache
目录
一,CentralCache的简单介绍
二,CentralCache的整体结构
三,CentralCache实现的详细代码
1,成员
2,函数
1, 获取单例对象的指针
2, FetchRangeObj函数
3,GetOneSpan函数实现
4,ReleaseListToSpans函数实现
一,CentralCache的简单介绍
CentralCache是高并发内存池这个项目的中间层。当第一层ThreadCache内存不够时便会向这一层申请内存。而CentralCache这一层的内存是从下一层的PageCache层申请的。如下图所示:
可以看到,CentralCahe层的实现和ThreadCache层的实现有异曲同工之妙。不过,在CentralCache链接的是由span块链接而成的spanlist。 spanlist上面便链接着自由链表。
span实现如下:
//定义一个Span结构,是CentralCache里面的大号内存
struct Span
{PAGEID _pageid = 0; //页号:在不同的程序下要有不同的类型size_t _n = 0; //一个span里的页的数量//双向链表的结构Span* _prev = nullptr;Span* _next = nullptr;//使用的个数,当_usecount为0时表示无人使用。int _useCont = 0;//Span节点中的小内存void* _freelist = nullptr;bool _used = false;int _objSize = 0;};//一个成员为Span类型的带头双向循环链表
class Spanlist
{
public:Spanlist(){//_head = PageCache::GetInstance()->_Newtospan.New();// FixedPoll<Span> _fixedpoll;_head = new Span;_head->_next = _head;_head->_prev = _head;}void PushFront(Span* span){insert(begin(), span);}Span* PopFront(){return remove(begin());}//插入新的节点到某一个位置上void insert(Span* pos, Span* Newnode){assert(pos );assert(Newnode);Span* prev = pos->_prev;prev->_next = Newnode;Newnode->_prev = prev;Newnode->_next = pos;pos->_prev = Newnode;}//删除某个位置上的节点Span* remove(Span* pos){assert(pos);assert(pos != _head);Span* Next = pos->_next;Span* Prev = pos->_prev;Prev->_next = Next;Next->_prev = Prev;pos->_next = nullptr;pos->_prev = nullptr;return pos;}//开始和结束函数Span* begin(){return _head->_next;}Span* end(){return _head;}//判空函数bool Empty(){return begin() == _head;}private://Span*节点Span* _head = nullptr;
public://桶锁std::mutex _mux;
};
二,CentralCache的整体结构
CentralCache的整体代码如下图所示:
//定义一个CentralCache类,类里面放的是SpanList*对象
//这个CentralCache要定义为单例
class CentralCache
{
public://获取单例static CentralCache* GetInstance(){return &_singleton;}//为threadCache向Central申请内存size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);Span* GetOneSpan(Spanlist* splist, size_t sz);// 将⼀定数量的对象释放到span中void ReleaseListToSpans(void* start, size_t byte_size);private:CentralCache() {};//不能删除只能私有CentralCache(const CentralCache&) = delete;CentralCache&operator =(CentralCache&) = delete;Spanlist _List[Nspan];static CentralCache _singleton;
};
正如上面的代码所示,CentralCache的实现依赖于它的四个函数和两个成员。
三,CentralCache实现的详细代码
1,成员
CentalCache函数的内部有两个成员。分别是一个spanlist的数组和一个单例对象。前面我已经介绍了spanlist这个结构。那为什么要使用单例呢? 使用单例能够提前生成CentralCache的实例,提高使用效率,方便在别处调用。
2,函数
//获取单例static CentralCache* GetInstance(){return &_singleton;}//为threadCache向Central申请内存size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);Span* GetOneSpan(Spanlist* splist, size_t sz);// 将⼀定数量的对象释放到span中void ReleaseListToSpans(void* start, size_t byte_size);
在CentralCache的类中实现了以上四个函数,接下来便详细讲解以上函数的实现。
1, 获取单例对象的指针
static CentralCache* GetInstance(){return &_singleton;}
这个函数的作用就是获取单例对象的指针。
2, FetchRangeObj函数
具体实现如下:
size_t CentralCache:: FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{//1,先获取大的spanint index = Index(size);CentralCache::_List[index]._mux.lock();Span* span = GetOneSpan(&_List[index], size);//断言下获取到的span和sizeassert(span);assert(size <= MaxBytes);//开始获取:end,start从span的自由链表处开始出发start = span->_freelist;end = span->_freelist;int i = 0;int actualNum = 1;while (i++ < batchNum-1 && NextObj(end) != nullptr){end = NextObj(end);actualNum++;}//截断span->_freelist = NextObj(end);NextObj(end) = nullptr;span->_useCont += actualNum;CentralCache::GetInstance()->_List[index]._mux.unlock();//返回相应的块数return actualNum;
}
这个函数通过调用对齐函数算出传入的大小对应的下标后再调用:GetOneSpan获得一个span并切分通过begin和end带出去交给ThreadCache。
3,GetOneSpan函数实现
具体实现代码如下:
Span* CentralCache::GetOneSpan(Spanlist* splist,size_t sz)
{//先查找范围Span* it = splist->begin();while (it != splist->end()){if (it->_freelist != nullptr){return it;}else{it = it->_next;}}//解锁:为了在下面的调用更加的快捷splist->_mux.unlock();//需要向下申请Span对象PageCache::GetInstance()->_mut.lock();Span* span = PageCache::GetInstance()->NewSpan(PageNum(sz));//从PageCache获取后标记被使用状态span->_used = true;PageCache::GetInstance()->_mut.unlock();//确定得到的span的内存范围,这个_pageid不是从0开始的而是一个地址>>PageShift后得到的char* start = (char*)(span->_pageid << PageShift);int bytes = span->_n << PageShift;char* end = start + bytes;//开始分割Spanspan->_freelist = start;start += sz;void* tail = span->_freelist;while (start < end){NextObj(tail) = start;tail = NextObj(tail);start += sz;}NextObj(tail) = nullptr;//再次加锁:涉及到了splist的操作splist->_mux.lock();//将span头插到spanlist中splist->PushFront(span);//返回spanreturn span;
这个函数的目的就是得到一个span。当CentralCache中没有sapn时便调用NewSpan函数向下一层的pageCache申请内存。并将span切成一块一块的小内存头插到对应的sanlist中。
4,ReleaseListToSpans函数实现
具体代码如下:
//ThreadCache释放内存还给CentralCache
void CentralCache::ReleaseListToSpans(void* start, size_t byte_size)
{ //计算是操作那个桶int index = Index(byte_size);_List[index]._mux.lock();//找spanSpan* span = PageCache::GetInstance()->MapObjectToSpan(start);//开始插入到span中while (start){//头插void* Next = NextObj(start);NextObj(start) = span->_freelist;span->_freelist = start;start = Next;//还回来一个就usecount减少一个,当_usecount减少到0就还给PageCachespan->_useCont--;if (span->_useCont == 0){_List[index]._mux.unlock();PageCache::GetInstance()->_mut.lock();//将CentralCache中的内存还给PageCachePageCache::GetInstance()->ReleaseSpanToPage(span);PageCache::GetInstance()->_mut.unlock();_List[index]._mux.lock();}}_List->_mux.unlock();}
这个函数实现的功能是将ThreadCache中的内存还给CentralCache层。 发生条件是:ThreadCache上的自由链表太长了。
原理:再ThreadCache层调用时ThreadCache层会调用该函数,并传入要归还的内存的首地址。因为每一个地址对应的页号是被设计过的,并且每一个页号和Span都会再PageCache中放入到哈希表中一一对应。所以,通过ThreadCache传入的地址便可以知道这个地址对应的Span,找到这个Span便可以进行头插从而将ThreadCache上过长的自由链表上的内存还给CentralCache。
四,总结
CentralCache层是高并发内存池的中间层。主要的作用便是为ThreadCache提供内存,提高ThreadCache申请内存的能力。