高并发内存池:Part-3——PageCache()+申请并发全链路测试通过。
·
✧(≖ ◡ ≖✿
目录
针对我个人的复习提醒:本章节首先分析了整体架构,复习时可首先回顾。
三级缓冲区图解
把握框架设计,熟悉整体架构。
各级缓冲区成员构成表

整体图解
三级缓冲区结构图

二级、三级缓冲区架构图

三缓存整体缩略图

ThreadCahe图解
CentralCache图解

PageCache图解

PageCache
PageCache是全局唯一的三级内存池中最底层的“大块内存批发商”,以多页(N*8K)为最小管理单位,负责向操作系统申请内存、按需切给CentralCache。(后续会并回收♻️不再使用的Span。)
三级缓冲区结构
注意:是双向带头循环链表

GetOneSpan()
功能:返回Span* 作为链表裁剪的对象(TargetNum、RealNum)。
定位:作为PageCache中获取Span的首次入口:
1.若直接存在则返回(判断依据为非空)。
2.从PageCache区找。
Span* CentralCache::GetOneSpan(SpanList& spl, size_t AlignSize)
{
//ThreadCache需要满足AlignSize的span
//spl内找
//spl._mtx.lock();桶锁依然锁着
Span* bg = spl.Begin();
Span* ed = spl.End();
while (bg != ed)
{
if (bg->_freeList)
return bg;
bg = bg->_nextSpan;
}
//spl内没有找到,spl所有span下为空/spl内无span
spl._mtx.unlock();
//为什么解锁后加锁让可能大于等于2的线程在三级缓冲区竞争锁?
//没有考虑到,若一级缓冲区恰好在间歇处释放内存回来到CentralCache的特定桶下造成
,直接返回的情况。
//去PageCache三级缓冲区寻找
PageCache::GetInstance()->_mtx.lock();
size_t npage = SizeClass::NumMovePage(AlignSize);
//获取span数,此处应为AlignSize不是课件内size
Span* span = PageCache::GetInstance()->GetSpans(npage);
PageCache::GetInstance()->_mtx.unlock();
//需加锁:桶锁or全局锁? 考虑到进入三级缓冲区的频率极低,使用全局锁
// //spl._mtx.lock();//线程独立,无需加锁
//仅切分span为单链形式即可,无需动属性值
//此处应为AlignSize不是课件内size
//char* start = (char*)span;
char* start = (char*)(span->_pageId << PAGE_SHIFT);
//忽略warning::(62,28): warning C4293: “<<”: Shift 计数为负或过大,其行为未定义
size_t bytes = (span->_n) << PAGE_SHIFT;
char* end = start + bytes;
void* front = span->_freeList = start;
//int i = 0;/*
//printf("i:%p\n", &i);*/
while (start < end)
{
//i++;
*(void**)front = start;
front = *(void**)front;
start += AlignSize;
}
*(void**)front = nullptr;
//插入到CentralCache特定桶下,需要加锁
spl._mtx.lock();
spl.HeadInsert(span);
return span;
//仅写完此模块(NewSpan未写)依看不出来spl._mtx的锁问题。
}
GetSpans()
功能:返回获取页数固定的Span* 。
直接访问PageCache:: _spanList[]的首次入口
1.直接定位查询。
2.向下查找,存在则切分。(new span)供切出去返回的实体。
3.系统级申请。
Span* PageCache::GetSpans(size_t pageNum)
{
//三级缓冲区获取span
//1.直接定位 2.向下找 3.VirtualAlloc()堆区申请
Span* span = PageCache::GetInstance()->_spanList[pageNum].Begin();
if (span->_nextSpan != span)
{
//PageCache拥有span非空链表
//SpanList的头删
PageCache::GetInstance()->_spanList[pageNum].ErasePos(span);
return span;//这也是ErasePos内不能delete的原因。要向上供给,这块内存属于
}
//[pageNum]下没有span
//2.在PageCache内向下找
for(int i = pageNum+1;i < 129;i++)
{
Span* _span = PageCache::GetInstance()->_spanList[i].Begin();
if (_span->_nextSpan != _span)
{
// 找到了需pageNum块,多出了i-pageNum块
// 考虑到此部分代码在"3."情况下的复用可以额外封装,也可以使用巧妙的递归解决。
// 头删
PageCache::GetInstance()->_spanList[i].ErasePos(_span);
//分割与插入
/*Span* insertSpan = (Span*)((PAGE_ID)_span + pageNum << PAGE_SHIFT);
insertSpan->_nextSpan = insertSpan;
insertSpan->_prevSpan = insertSpan;*/
//强制类型转换造成的内存越界访问
Span* insertSpan = new Span;//堆申请!!!
insertSpan->_n = i - pageNum;
insertSpan->_pageId = _span->_pageId+pageNum;
_span->_n = pageNum;
_span->_pageId += pageNum;
PageCache::GetInstance()->_spanList[i - pageNum].HeadInsert(insertSpan);//剩余块头插
return _span;
}
}
//3.[i]向下全空,向系统申请128页--128*8K
void* ptr = SizeClass::SystemAlloc(128);//无需检查失败VirtualAlloc()
//Span* test = new Span;
Span* m128span = (Span*)ptr;//调用自动生成的拷贝赋值,无法自己指向自己
//
m128span->_nextSpan = m128span;
m128span->_prevSpan = m128span;
m128span->_n = 1;
m128span->_pageId = (PAGE_ID)m128span >> PAGE_SHIFT;
_spanList[128].HeadInsert(m128span);
return GetSpans(pageNum);
}
mutex分析
两个互斥锁:SpanList桶锁+PageCache的全局互斥锁。

视频口述

一级缓冲区衔接二三级缓冲区
二级、三级缓冲区锁管理
回收逻辑过于简单(仅作以下图示)

感谢支持,长期连载。
欢迎关注
更多推荐




所有评论(0)