目录
一,CentralCache的简单介绍
二,CentralCache的整体结构
三,CentralCache实现的详细代码
1,成员
2,函数
1, 获取单例对象的指针
2, FetchRangeObj函数
3,GetOneSpan函数实现
4,ReleaseListToSpans函数实现
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 _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类,类里面放的是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的实现依赖于它的四个函数和两个成员。
CentalCache函数的内部有两个成员。分别是一个spanlist的数组和一个单例对象。前面我已经介绍了spanlist这个结构。那为什么要使用单例呢? 使用单例能够提前生成CentralCache的实例,提高使用效率,方便在别处调用。
//获取单例
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的类中实现了以上四个函数,接下来便详细讲解以上函数的实现。
static CentralCache* GetInstance()
{
return &_singleton;
}
这个函数的作用就是获取单例对象的指针。
具体实现如下:
size_t CentralCache:: FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
//1,先获取大的span
int index = Index(size);
CentralCache::_List[index]._mux.lock();
Span* span = GetOneSpan(&_List[index], size);
//断言下获取到的span和size
assert(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。
具体实现代码如下:
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;
//开始分割Span
span->_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);
//返回span
return span;
这个函数的目的就是得到一个span。当CentralCache中没有sapn时便调用NewSpan函数向下一层的pageCache申请内存。并将span切成一块一块的小内存头插到对应的sanlist中。
具体代码如下:
//ThreadCache释放内存还给CentralCache
void CentralCache::ReleaseListToSpans(void* start, size_t byte_size)
{
//计算是操作那个桶
int index = Index(byte_size);
_List[index]._mux.lock();
//找span
Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
//开始插入到span中
while (start)
{
//头插
void* Next = NextObj(start);
NextObj(start) = span->_freelist;
span->_freelist = start;
start = Next;
//还回来一个就usecount减少一个,当_usecount减少到0就还给PageCache
span->_useCont--;
if (span->_useCont == 0)
{
_List[index]._mux.unlock();
PageCache::GetInstance()->_mut.lock();
//将CentralCache中的内存还给PageCache
PageCache::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申请内存的能力。