
💡Yupureki:个人主页
✨个人专栏:《C++》 《算法》《Linux系统编程》《高并发内存池》
🌸Yupureki🌸的简介:

目录
[2. Span与SpanList的构造](#2. Span与SpanList的构造)
[3. CentralCache类初步实现](#3. CentralCache类初步实现)
1.CentralCache结构初识
centralcache也是一个哈希桶结构,他的哈希桶的映射关系跟threadcache是一样的。不同的是他的每个哈希桶位置挂是SpanList链表结构,不过每个映射桶下面的span中的大内存块被按映射关系切成了一个个小内存块对象挂在span的自由链表中。
span是一个结构体,专门用来管理一定大小(如8字节,16字节)的内存块的自由链表

申请内存:
- 当threadcache中没有内存时,就会批量向centralcache申请一些内存对象。centralcache也有一个哈希映射的spanlist,spanlist中挂着span,从span中取出对象给thread cache,这个过程是需要加锁的,不过这里使用的是一个桶锁,尽可能提高效率。
- centralcache映射的spanlist中所有span的都没有内存以后,则需要向pagecache申请一个新的span对象,拿到span以后将span管理的内存按大小切好作为自由链表链接到一起。然后从span中取对象给thread cache。
- centralcache的中挂的span中use_count记录分配了多少个对象出去,分配一个对象给threadcache,就++use_count
释放内存:
- 当thread_cache过长或者线程销毁,则会将内存释放回centralcache中的,释放回来时-use_count。当use_count减到0时则表示所有对象都回到了span,则将span释放回pagecache,pagecache中会对前后相邻的空闲页进行合并。
2. Span与SpanList的构造
Span是专门用来管理相同大小的内存块的自由链表的结构体,因此含有以下成员:
- 自由链表中内存块的个数
- 首个内存块对应的页码
- next指针(在SpanList中指向下一个Span)
- prev指针
- 单个内存块的大小
- 使用内存块的个数
- 指向内存块自由链表的指针
- 是否被使用的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;
}