《C++实战项目-高并发内存池》4.CentralCache构造

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》《Linux系统编程》《高并发内存池》


🌸Yupureki🌸的简介:


目录

1.CentralCache结构初识

[2. Span与SpanList的构造](#2. Span与SpanList的构造)

[3. CentralCache类初步实现](#3. CentralCache类初步实现)


1.CentralCache结构初识

centralcache也是一个哈希桶结构,他的哈希桶的映射关系跟threadcache是一样的。不同的是他的每个哈希桶位置挂是SpanList链表结构,不过每个映射桶下面的span中的大内存块被按映射关系切成了一个个小内存块对象挂在span的自由链表中。

span是一个结构体,专门用来管理一定大小(如8字节,16字节)的内存块的自由链表

申请内存:

  1. 当threadcache中没有内存时,就会批量向centralcache申请一些内存对象。centralcache也有一个哈希映射的spanlist,spanlist中挂着span,从span中取出对象给thread cache,这个过程是需要加锁的,不过这里使用的是一个桶锁,尽可能提高效率。
  2. centralcache映射的spanlist中所有span的都没有内存以后,则需要向pagecache申请一个新的span对象,拿到span以后将span管理的内存按大小切好作为自由链表链接到一起。然后从span中取对象给thread cache。
  3. centralcache的中挂的span中use_count记录分配了多少个对象出去,分配一个对象给threadcache,就++use_count

释放内存:

  1. 当thread_cache过长或者线程销毁,则会将内存释放回centralcache中的,释放回来时-use_count。当use_count减到0时则表示所有对象都回到了span,则将span释放回pagecache,pagecache中会对前后相邻的空闲页进行合并

2. Span与SpanList的构造

Span是专门用来管理相同大小的内存块的自由链表的结构体,因此含有以下成员:

  1. 自由链表中内存块的个数
  2. 首个内存块对应的页码
  3. next指针(在SpanList中指向下一个Span)
  4. prev指针
  5. 单个内存块的大小
  6. 使用内存块的个数
  7. 指向内存块自由链表的指针
  8. 是否被使用的bool变量
cpp 复制代码
struct Span
{
	PAGE_ID _page_id = 0;
	size_t _num = 0;
	Span* _next = nullptr;
	Span* _prev = nullptr;
	size_t _object_size = 0;
	size_t _use_count = 0;
	void* _free_list = nullptr;
	bool _is_use = false;
};

SpanList是数个Span串起来的链表,因此有指向第一个Span的指针

由于可能有多个线程访问同一个SpanList,因此需要加锁

SpanList的操作,如链表中的增删查改我们也需要实现

cpp 复制代码
class SpanList
{
public:
	SpanList()
	{
		_head = new Span;
		_head->_next = _head;
		_head->_prev = _head;
	}
	void PushFront(Span* span)
	{
		Insert(Begin(), span);
	}
	Span* PopFront()
	{
		Span* span = _head->_next;
		Erase(_head->_next);
		return span;
	}
	void Insert(Span* span, Span* newspan)
	{
		assert(span && newspan);
        Span* prev = span->_prev;
        prev->_next = newspan;
        newspan->_prev = prev;
        newspan->_next = span;
        span->_prev = newspan;
	}
	void Erase(Span* span)
	{
		assert(span && span != _head);
		Span* prev = span->_prev;
		Span* next = span->_next;
		prev->_next = next;
		next->_prev = prev;
	}
	void Lock()
	{
		_mtx.lock();
	}
	void Unlock()
	{
		_mtx.unlock();
	}
	Span* Begin()
	{
		return _head->_next;
	}
	Span* End()
	{
		return _head;
	}
	bool Empty()
	{
		return _head->_next == _head;
	}
private:
	Span* _head;
	std::mutex _mtx;//互斥锁
};

3. CentralCache类初步实现

由于一个内存池中只有一个CentralCache,因此为了安全性,我们使用单例模式

cpp 复制代码
//单例模式
class CentralCache
{
private:
	CentralCache()
		:_spanlists(NFREE_LIST)
	{
	}
	CentralCache(const CentralCache&) = delete;
public:
	static CentralCache& GetInstance()
	{
		return _cinst;//获取单例
	}
	// 获取一个非空的span
	Span * GetOneSpan(SpanList & list, size_t byte_size);

	// 从中心缓存获取一定数量的对象给thread cache
	size_t FetchRangeObj(void** start, void** end, size_t batchNum, size_t size);
    
    //将多余的Span换给PageCache
	void ReleaseListToSpans(void* start, size_t bytes);
private:
	std::vector<SpanList> _spanlists;
	static CentralCache _cinst;
};

由于PageCache我们还没有实现,因此ReleaseListToSpans函数暂时不实现

FetchRangeObj:从中心缓存获取一定数量的对象给thread cache

threadcache会把一批内存块拿走,期望的个数是batchNum

同时传入了start和end指针,由于内存块是以链表的形式串起来的,start指向起始内存块的地址,end指向最后的内存块的地址

CentralCache会在SpanList中挑一个不为空的Span,不管这个Span中内存块剩余多少。

如果不够batchNum,有多少给多少;如果够了,就拿batchNum个

cpp 复制代码
size_t CentralCache::FetchRangeObj(void** start, void** end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);//计算下标
	_spanlists[index].Lock();//锁住
	Span* span = GetOneSpan(_spanlists[index], size);//从SpanList中挑一个不为空的Span
	*start = span->_free_list;
	*end = *start;
    int i = 0;
    if (*start == nullptr)
    {
        _spanlists[index].Unlock();//SpanList中所有的Span都为空
        return 0;
    }
    size_t actual_num = 1;
    while (i < static_cast<int>(batchNum) - 1 && Next_Obj(*end))//如果不够,有多少拿多少
    {
        *end = Next_Obj(*end);
        ++i;
        ++actual_num;
    }
    span->_free_list = Next_Obj(*end);
    Next_Obj(*end) = nullptr;
	_spanlists[index].Unlock();//解锁
	return actual_num;//真实拿走内存块的个数
}

GetOneSpan:从SpanList中挑一个不为空的Span

如果SpanList中有一个Span不为空,不管里面有几个内存块,直接返回

如果全部为空,那么就得向PageCache申请一个新的Span,我们先假设得到了那个Span

PageCache中给的内存是一个连续的完整的内存,而不是size大小的一个个的小内存块串在一起的链表

因此我们需要对这个大内存块切分,然后用链表串在一起

cpp 复制代码
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	// 从给定的Span链表中获取一个包含自由对象的Span
	Span* it = list.Begin();
	while (it != list.End())
	{
		if (it->_free_list != nullptr)
			return it;
		it = it->_next;
	}
	list.Unlock();
	// 如果没有找到合适的Span,则从PageCache获取新的Span
	PageCache::GetInstance().Lock();
	Span* span = PageCache::GetInstance().NewSpan(SizeClass::SizeMovePage(size));
	span->_object_size = size;
	span->_is_use = true;
	PageCache::GetInstance().Unlock();
	// 初始化新Span的自由对象链表
	char* start = (char*)(span->_page_id << PAGE_SHIFT);
	size_t bytes = (span->_num << PAGE_SHIFT);
	char* end = start + bytes;
	span->_free_list = start;
	start += size;
	void* tail = span->_free_list;
	while (start < end)
	{
		Next_Obj(tail) = start;
		tail = start;
		start += size;
	}
	Next_Obj(tail) = nullptr;
	list.Lock();
	list.PushFront(span);
	return span;
}
相关推荐
样例过了就是过了1 小时前
LeetCode热题100 全排列
数据结构·c++·算法·leetcode·dfs
2401_898075121 小时前
分布式系统监控工具
开发语言·c++·算法
OKkankan2 小时前
撕 STL 系列:封装红黑树实现 mymap 和 myset
java·c++·算法
xh didida2 小时前
数据结构--实现链式结构二叉树
c语言·数据结构·算法
ab1515172 小时前
3.15二刷基础90、105、106、110
数据结构·c++·算法
APIshop2 小时前
Java调用亚马逊商品详情API接口完全指南
java·开发语言·python
寻寻觅觅☆2 小时前
东华OJ-进阶题-10-分解质因数(C++)
数据结构·c++·算法
lsx2024062 小时前
PostgreSQL中的NULL处理
开发语言
Darkwanderor2 小时前
数据结构——ST表和RMQ问题
数据结构·c++·动态规划·st表·rmq问题