高并发内存池(五)Thread Cache、Central Cache回收功能的实现

目录

一、Thread Cache的回收实现

1.1Thread Cache回收框架

1.2Thread Cache回收实现

二、Central Cache

2.1Central Cache回收框架

2.2Central Cache回收实现


一、Thread Cache的回收实现

1.1Thread Cache回收框架

在实现完整的高并发内存池内存分配逻辑以后,回收逻辑就变得清晰明了很多了,怎么分出去的就怎么收回来,不过相比较与分配逻辑,回收则是从小块内存合并到大块内存最后再次挂入Page Cache进行下次使用,本文博主对Thread Cache的实现进行了详细的拆分及讲解。

 因为小块内存的使用频率相比于大块内存是更多的,而Thread Cache也是直接面对用户的第一层,所以在大逻辑上,当用户要释放数据时,就先调用Thread Cache的释放函数:

这里只是先对释放的逻辑进行初步实现,针对超大内存的申请,博主会在后面对代码进行优化。

static void ConcurrentFree(void* ptr,size_t size)//释放
{
	assert(pTLSThreadCache);

	pTLSThreadCache->Deallocate(ptr,size);
}

 Thread Cache释放内存遵循以下两个特点:

1. 当释放的内存小于256KB时将内存释放回thread cache中,计算size所映射的自由链表桶组成的_freelist中的位置下标index,然后将对象Push 到_freelists[index]中。

2. 当哈希桶中某个_freelist链表的长度过长,空闲内存块个数达到阈值(一般将_MaxSize也就是每个链表中单次的最大申请数作为这个值),则回收一部分内存对象到Central Cache中。

1.2Thread Cache回收实现

void ThreadCache::Deallocate(void* ptr, size_t size)//释放空间,顺便收集内存碎片返还给central cache
{
	assert(ptr);
	assert(size <= MAX_BYTES);
	//size_t alignSize = SizeClass::RoundUp(size);
	//还回来时不需要再去做对齐,因为申请时申请到的就是对齐完毕的,释放时肯定是桶内存在的

	//去找所要释放的size所在的桶,然后将要释放的对象进行Push插入
	size_t index = SizeClass::Index(size);
	_freelist[index].Push(ptr);

	//当一个桶内链表长度大于一次批量申请的内存时就切一段list还给central cache
	if (_freelist[index].Size() >= _freelist[index].MaxSize())
	{
		ListTooLong(_freelist[index], size);
	}

}                                               
void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
	void* start = nullptr;
	void* end = nullptr;
	list.ringpop(start, end, list.MaxSize());

	CentralCache::GetInstance()->ReleaseListToSpans(start, size);//将当前进程中有大量空闲内存的freelist切出一部分还给Central Cache
}

Thread Cache中的回收逻辑分为两部分:

1、将小块内存放到对应的桶中的Dellocate

2、当桶中内存块达到一定数量后将其放回Central Cache中。

二、Central Cache

2.1Central Cache回收框架

1、当Thread_cache中某个桶出现内存过长或者线程退出销毁,则会将内存释放回central cache同样的下标所对应的桶中,释放回来时通过对链表中一个个内存块所算出的页号的查找,对应到具体的Span中,并将该Span中的use_count--。

2、当use_count减到0时则表示所有对象都回到了span,这时则将span重新放回回page cache, 因为Span中存储的内存大小都是整数页,所以在page cache中会对前后相邻的空闲页进行合并。

2.2Central Cache回收实现

将来自某个Thread Cache中空闲的内存块链表接收以后,找到和其大小相对应的桶后,对其地址计算页号,找到该桶中对应的Span,然后将其头插到Span中的freelist之中,再将该Span的usecount--,然后去处理链表中的下一个内存块,直到将整个链表中的内存块都放到对应的Span当中。

当Span中的usecount为0时,就要将该Span从对应的桶中删除(并不是释放了只是从Central Cache中删除),将Span中的数据都清理干净,然后将Span再交给Page Cache的回收函数对其进行前后空闲页合并和回收。

//回收从Thread Cache传回来的内存
void CentralCache::ReleaseListToSpans(void* start, size_t size)//strat:传回来链表的起始地址
{
	size_t index = SizeClass::Index(size);
	//计算出从此处thread Cache中传回的批量内存块属于central cahce中的哪个spanlist后,
	//对其进行加锁,将内存放回去
	_spanList[index]._mtx.lock();
	while (start)
	{
		void* next = NextObj(start);

		//关于为什么要查找页号才能确认span,因为虽然我们可以确认当前size在central cache的哪个桶
		//但是每个桶中有大量的span,如果一个一个span去便利对比根据地址计算后的页号
		//有n个span,每个sapn有n个切好的小内存,复杂度直接n方了铁铁,所以直接在pagecache切分span时就建立页号和某一具体span的地址

		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);//查找当前内存块所对应的页号
		NextObj(start) = span->_freeList;
		span->_freeList = start;
		span->_useCount--;//将span中记录已经使用的_usecount--

		//当这个span中的usecount=0时,说明当前span切分出去的小块内存全部回来了
		//此时就可以将当前span回收给page cache
		if (span->_useCount == 0)
		{
			_spanList[index].Erase(span);
			span->_freeList = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;
			//span->_isUse=false;不能在这里直接置为false,如果此时再有其他线程归还,前后合并时            
            //如果合并到这里的Span,就会出现大问题
			//此时这个span已经和central cache的spanlist完全断开了连接
			//直接解锁允许其他线程继续访问当前桶
			_spanList[index]._mtx.unlock();

			//去调用Page Cache的大锁
			PageCache::GetInstance()->_pageMtx.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMtx.unlock();

			_spanList[index]._mtx.lock();//重新上锁,去处理下一个小块内存
		}
		start = next;//继续去处理下一个threadcache传回来的内存块
	}
	_spanList[index]._mtx.unlock();
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C+五条

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值