高并发内存池(4)——实现CentralCache

目录

一,CentralCache的简单介绍

 二,CentralCache的整体结构

三,CentralCache实现的详细代码

1,成员

 2,函数

1, 获取单例对象的指针

2, FetchRangeObj函数

3,GetOneSpan函数实现

 4,ReleaseListToSpans函数实现


一,CentralCache的简单介绍

CentralCache是高并发内存池这个项目的中间层。当第一层ThreadCache内存不够时便会向这一层申请内存。而CentralCache这一层的内存是从下一层的PageCache层申请的。如下图所示:

高并发内存池(4)——实现CentralCache_第1张图片

可以看到,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的整体代码如下图所示:

//定义一个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,先获取大的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。

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;

	//开始分割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中。

 4,ReleaseListToSpans函数实现

具体代码如下:

//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申请内存的能力。

你可能感兴趣的:(高并发内存池项目笔记,c++,学习)