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

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

💡Yupureki:个人主页

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


🌸Yupureki🌸的简介:


目录

1.CentralCache结构初识

2. Span与SpanList的构造

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变量
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的操作,如链表中的增删查改我们也需要实现

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,因此为了安全性,我们使用单例模式

//单例模式 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个

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大小的一个个的小内存块串在一起的链表

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

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; } 

Read more

C++高精度时间库——<chrono>

C++高精度时间库——<chrono>

在前面学习C语言我们就已经学习了C语言的时间库<time.h>。它操作简单、是全平台行为一致的时间库。但是它却存在着很多的问题——类型不安全,精度较低(通常为秒),扩展只能依赖编译器特制的宏。         而现代项目中如果要进行性能测试、时间间隔计算的话,C语言的<time.h>显然是不够的。因此在C++11中,引入了<chrono>这个时间库。接下来我们就来一次学习一下。         相关的英文文档为:Standard library header <chrono> (C++11) - cppreference.com         相关的测试代码已经上传至作者的个人gitee:楼田莉子/CPP代码学习 目录 设计哲学 库中的单位         时长单位(Duration Units)         时钟单位(

By Ne0inhk
华为OD机试真题-网上商城优惠活动 (Py/Java/C/C++/Js/Go)

华为OD机试真题-网上商城优惠活动 (Py/Java/C/C++/Js/Go)

华为OD机试双机位C卷-网上商城优惠活动 华为OD机试双机位C卷 - 华为OD上机考试双机位C卷 100分题型 华为OD机试双机位C卷真题目录点击查看: 华为OD机试双机位C卷真题题库目录|机考题库 + 算法考点详解 题目描述 某网上商场举办优惠活动,发布了满减、打折、无门槛3种优惠券。 分别为:每满100元优惠10元,无使用数限制,如100 ~ 199元可以使用1张减10元,200 ~ 299可使用2张减20元,以此类推; 92折券,1次限使用1张,如100元,则优惠后为92元; 无门槛5元优惠券,无使用数限制,直接减5元。 优惠券使用限制每次最多使用2种优惠券,2种优惠可以叠加(优惠叠加时以优惠后的价格计算),以购物200元为例,可以先用92折券优惠到184元,再用1张满减券优惠10元,最终价格是174元,也可以用满减券2张优惠20元为180元,再使用92折券优惠到165(165.6向下取整),不同使用顺序的优惠价格不同,以最优惠价格为准。在一次购物种,同一类型优惠券使用多张时必须一次性使用,不能分多次拆开使用(不允许先使用1张满减券,再用打折券,再使用一张满减券)。

By Ne0inhk
【C++】第二十一节—一文详解 | 红黑树实现(规则+效率+结构+插入+查找+验证)

【C++】第二十一节—一文详解 | 红黑树实现(规则+效率+结构+插入+查找+验证)

Hi,我是云边有个稻草人......who?me,be like——→ 《C++》本篇文章所属专栏—持续更新中—欢迎订阅 目录 一、红黑树的概念 1.1 红黑树的规则 1.2 思考⼀下,红黑树如何确保最长路径不超过最短路径的2倍的? 1.3 红黑树的效率 二、红黑树的实现 2.1 红黑树的结构 2.2 红⿊树的插⼊ 【红⿊树树插⼊⼀个值的⼤概过程】 【情况1:变⾊】 【情况2:单旋+变⾊】 【情况2:双旋+变⾊】 2.3 红黑树的插入代码实现 2.4

By Ne0inhk
华为OD机试双机位C卷:日志解析(C/C++/Java/Python/Go/JS)

华为OD机试双机位C卷:日志解析(C/C++/Java/Python/Go/JS)

日志解析 2026华为OD机试双机位C卷 - 华为OD上机考试双机位C卷 200分题型 华为OD机试双机位C卷真题目录点击查看: 华为OD机试双机位C卷真题题库目录|机考题库 + 算法考点详解 题目描述 你是一个运维工程师,你同时负责n个系统的运维工作,已知每个系统每天会都从现场采集大量的现网运行日志(错误日志、接口日志等)下来生成一个日志文件,每个系统采集下来的日志文件大小均不相同。为了解析这些日志,你给每个系统配备了一台默认服务器进行日志解析,且此台服务器只能给本系统使用,由于所配置的服务器规则均相同,因为解析日志的速度也是相同的,即每秒钟可以解析defaultCnt条日志。 现在你发现解析的速度达不到预期,但你手头上还有一部分额外的资源可以使用,这些资源可以在任意时刻配置给任意一台服务器。但有个限制,那就是同一时刻只能配给其中一台服务器器,且服务器器是能整合全部额外资源,当然在下一秒钟即可配备给另外一台服务器。某一台服务器配备了额外资源以后,则每秒钟会增加解析extraCnt条日志,即每秒可解析(defaultCnt+extraCnt)条日志。 输入描述 输入一

By Ne0inhk