C++项目——高并发内存池(3)--central cache整体设计

news/2024/4/26 8:43:35/文章来源:https://blog.csdn.net/qq_68741368/article/details/129139341

1.central cache的介绍

1.1框架思想

1.1.1哈希映射

centralcache其实也是哈希桶结构的,并且central cachethread cacha哈希映射关系是一致的。目的为了,当thread cache某一个哈希桶下没有内存块时,可以利用之前编写的SizeClass::Index() 直接访问centralcache对应的哈希桶结构以拿到内存空间。

不同的是thread cache桶结构下面挂的是一个一个切好的定长内存块,而central cache桶结构下面挂的是一个一个的SpanList结构,其中的Span是管理以页为单位的大块内存,Span中大块内存被按照映射关系切成一个个小内存块,挂在Span自由链表中。

1.1.2 单例模式

因为,在高并发线程池的整体项目框架下,所有的thread cache都共享一个central cache,所以将它设计成单例模式是和逻辑且安全的。

1.1.3 加桶锁

但是central cache是线程之间共有的,所以线程从这里申请内存时,是需要加锁的。为了减少锁的竞争,central cache使用的是桶锁,意思是,当线程1和线程2同时向同一个桶申请内存时,才会有竞争,可以减少阻塞。

1.2 cenctral cache的作用

  • 分配更多的内存给thread cache

因为仅在thread cache给予内存是无锁的,效率更高。所以当它对应哈希映射的哈希桶内没有内存块时,就需要central cache来提供,那如果一次仅仅提供一个对应的内存块,冲突和阻塞现象会加重,即需要一次分配给一串。

  • 中央调度工作

由于thread cache一直向centralcache申请某一字节的内存空间,而当这些内存释放时,就会挂在thread cache下的哈希桶结构中,而导致其他线程再向centralcache申请时,没有对应的内存空间,所以centralcache还需要起到调度的作用,当thread cache桶结构挂的内存块过多时,需要拿回来。

 2.Span SpanList

 Span

struct Span
{PAGE_ID _pageId=0;// 大块内存起始页的页号size_t _n=0;//数量Span* _next=nullptr;Span* _prev=nullptr;size_t _useCount=0;//被使用的个数void* _freeList=nullptr;//切好的小块内存的自由链表
};

给默认值的意义是可以偷懒不写构造函数。


SpanList

之前提到central cache需要桶锁,总不能208个一个一个都显示的写吧。而且锁作为公有成员变量没有什么安全问题。所以直接在哈希桶下挂的SpanList中封装一把锁,这样每个桶就都有一把锁。

//每个桶下面挂着的就是SpanList
class SpanList
{ 
public:std::mutex _mtx;//桶锁SpanList(){_head = new Span;_head->_prev = _head;_head->_next = _head;}void Insert(Span* pos, Span* newSpan){assert(pos);assert(newSpan);Span* prev = pos->_prev;prev->_next = newSpan;newSpan->_prev =prev;newSpan->_next = pos;pos->_prev = newSpan;}void Erase(Span* pos){assert(pos);assert(pos != _head);Span* prev = pos->_prev;Span* next = pos->_next;prev->_next = next;next->_prev = prev;//注意不需要free 因为我用完要放到自由链表里面 供以后使用//从当前剔除就行}
private:Span* _head=nullptr;};

3.Central Cache的实现

 声明

class CentralCache
{
public:static CentralCache* GetInstance(){return &_sInst;}//从中心缓冲中获取多少数量的对象给thread cachesize_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);// 获取一个非空的spanSpan* GetOneSpan(SpanList& list, size_t byte_size);
private:CentralCache(){}CentralCache(const CentralCache& abc) = delete;
private:static CentralCache _sInst;//记得类外初始化SpanList _spanLists[NFREELISTS]; 
};

 部分实现

#include "CentralCache.h"
CentralCache CentralCache::_sInst;Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{// 需要在自身判断 也需要在page cache中判断 所以以后在写// 这里只是为了通过编译return nullptr;
}

3.1 ThreadCache::FetchFromCentralCache()

记得在thread cache中我们没有实现的这个函数吗,现在我们既然有了CentralCache类型了,现在来构思一下这个函数。

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)

首先这个函数是需要返回一个地址的,就是我们将分割好的内存的起始位置当做返回值返回。其次我们通过上面得知CentralCache一次可不仅仅分给我们一个内存块,而是多个。

因此

  • 我们需要知道当空间充足时,分配几个。空间不充足时,分配几个。
  • 第二我们也需要知道这群内存块的最后一个的起始位置,方便我们把这一串挂到threadcache下的哈希桶结构内。
  • 如何将一串连续的空间都挂到桶上呢?难道每次都一个个的放吗?

3.1.1 SizeClass::NumMoveSize()

该用于确定分配数量的上限,即不可能分配出比该函数返回值更大的数量了。

class SizeClass
{
public://... 之前写过的略static size_t NumMoveSize(size_t size){assert(size > 0);int num = MAX_BYTES / size;if (num < 2) num = 2;//如果内存块比较大 最少一次拿走两个if (num > 512) num = 512;//如果内存块很小 最多一次拿走512个return num;}
};

假设size=8,难道就真的按照返回值512,一次性给一个thread cache分配出512个内存块吗?首先如果是这样,那么只会允许两次向central cache申请,其次给的空间过多,都在某一线程的自由链表挂着,其他线程想要用的时候拿不到,会使central cache的调度次数大大增加。

 

所以我们还需要一个缓慢增加申请数量的代码。

如果一个桶经常向central cache申请空间,说明该映射下的内存块需求量大,我们就依次进行一个缓慢增加的策略,随着他申请次数的增多,一次分配的内存块数量也增多。那要如何保存他申请的次数呢?

直接封装在thread cache桶(FreeList)里面

class FreeList
{
public:void Push(void* obj){}void* Pop(){}bool Empty(){}size_t& Maxsize(){return _maxSize;}
private:void* _freeList=nullptr;size_t _maxSize = 1;
};

3.1.2  FreeList::PushRange()

static void*& NextObj(void* obj)
{return *(void**)obj;
}
class FreeList
{
public:void PushRange(void* start, void* end){NextObj(end) = _freeList;_freeList = start;}size_t& Maxsize(){return _maxSize;}
private:void* _freeList=nullptr;size_t _maxSize = 1;
};

3.1.3 函数实现

这里假设我们已经实现好了一个函数FetchRangeObj(),它可以直接返回申请空间的真实数量。

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{size_t batchNum = std::min(SizeClass::NumMoveSize(size),_freeLists[index].Maxsize());if (_freeLists[index].Maxsize() == batchNum) _freeLists[index].Maxsize() += 1;void* start = nullptr;void* end = nullptr;//batchNum只是理想的 申请的对象 但事实是可能只能申请一个 而申请不到期望值size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, size);if(actualNum == 1){assert(start == end);return start;}else{_freeLists[index].PushRange(NextObj(start), end);return start;}return nullptr;
} 

 3.2 CentralCache::FetchRangeObj()

经过上面的分析,ThreadCache::FetchFromCentralCache()中需要用到CentralCache::FetchRangeObj(),以拿到start end指针,以及确切的可以分配给thread cache的内存块数量。

首先,需要通过size找到哈希桶的下标,然后找一个非空的Span,用来申请空间。

其次,如果span->_freeList不为空就说明至少有一个内存块,所以actualNum初始值为1,所以end也只需向后走batchNum-1次,将[start,end]这部分拿走,使_freeList指向end后面的那个节点,即使那个节点是空也符合逻辑。

然后,依据batchNum获取内存块数,可能会不够,但至少有一个可以被拿走使用解决问题,所以处理的逻辑是,如果不能获取期望的内存块数,那就有几个拿几个。当end的后面为空时停止。

size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{//首先依据申请的对齐字节数判断在哪一个哈希桶内size_t index = SizeClass::Index(size);//上锁_spanLists[index]._mtx.lock();//要给别人分配内存 首先从自己这里找一个非空的Span 自己没有就从page cache找一个Span* span = GetOneSpan(_spanLists[index],size);assert(span);assert(span->_freeList);start = span->_freeList;end = start;int i = 0;size_t actualNum = 1;while (i < batchNum - 1 && NextObj(end) != nullptr){end = NextObj(end);i++;}span->_freeList = NextObj(end);NextObj(end) = nullptr;_spanLists[index]._mtx.unlock();return actualNum;
}

4.thread cache向central cache申请空间的流程图

流程图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_72219.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RPC编程:RPC概述和架构演变

RPC编程系列文章第一篇一&#xff1a;引言1&#xff1a;本系列文章的目标2&#xff1a;RPC的概念二&#xff1a;架构的演变过程1&#xff1a;单体架构1)&#xff1a;概念2)&#xff1a;特点3)&#xff1a;优缺点2&#xff1a;单体架构水平扩展1)&#xff1a;水平拓展的含义2)&a…

整车电源的几种模式:OFF/ACC/RUN/CRANK

本文框架1.前言2. 四种电源模式2.1 OFF模式2.2 ACC模式2.3 ON模式2.4 CRANK模式3. KL15/KL301.前言 在诊断或者网络管理相关模块开发对客户的需求进行梳理时&#xff0c;经常会看到客户对不同车辆模式下处理策略的需求&#xff0c;如果前期没接触过这几种模式&#xff0c;可能…

【C++】初识CC++内存管理

前言 我们都知道C&C是非常注重性能的语言&#xff0c;因此对于C&C的内存管理是每一个C/C学习者必须重点掌握的内容&#xff0c;本章我们并不是深入讲解C&C内存管理&#xff0c;而是介绍C&C内存管理的基础知识&#xff0c;为我们以后深入理解C&C内存管理做铺…

【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析

如果觉得我的分享有一定帮助&#xff0c;欢迎关注我的微信公众号 “码农的科研笔记”&#xff0c;了解更多我的算法和代码学习总结记录。或者点击链接扫码关注【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析 【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析 原文&…

Ardiuno-交通灯

LED交通灯实验实验器件&#xff1a;■ 红色LED灯&#xff1a;1 个■ 黄色LED灯&#xff1a;1 个■ 绿色LED灯&#xff1a;1 个■ 220欧电阻&#xff1a;3 个■ 面包板&#xff1a;1 个■ 多彩杜邦线&#xff1a;若干实验连线1.将3个发光二极管插入面包板&#xff0c;2.用杜邦线…

【JUC2022】第二章 多线程锁

【JUC2022】第二章 多线程锁 文章目录【JUC2022】第二章 多线程锁一、乐观锁与悲观锁1.悲观锁2.乐观锁二、八锁案例1.标准情况&#xff0c;有a、b两个线程&#xff0c;请问先打印邮件还是短信【结果&#xff1a;邮件】2.sendEmail方法中加入暂停3秒钟&#xff0c;请问先打印邮件…

华为OD机试 - 最小传递延迟(C++) | 附带编码思路 【2023】

刷算法题之前必看 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:https://blog.csdn.net/hihell/category_12199283.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 华为OD机试题…

随机数与蒙特卡洛方法及Python实现

0 建议学时 4学时 1 引入 1.1 随机数与采样 客观世界的某些行为&#xff0c;结果具有随机性&#xff1a; 掷骰子、投硬币&#xff1b; 等待公交车的时间&#xff1b; 种子发芽的比例&#xff1b; … 1.2 随机数函数 1.2.1 random模块 Python的random模块中提供了若干生成…

RFID盘点软件为企业提供RFID固定资产管理方案

随着科技的发展&#xff0c;固定资产管理系统也经过了一些变革&#xff0c;从刚开始的单机版逐渐发展成SaaS版本&#xff0c;物联网版本等。从刚开始只支持条形码到支持二维码、RFID码。RFID固定资产管理系统上线后&#xff0c;通过给每个实物资产绑定一个RFID码标签后&#xf…

接口测试流程是怎样的?

接口测试流程是怎样的&#xff1f;总所周知&#xff0c;接口测试流程是怎样的&#xff1f;总所周知接口测试在软件测试中是一个非常重要的一部分&#xff0c;其主要目的是测试应用程序的接口是否能够按照规范要求与其他系统或组件进行交互&#xff0c;以及在不同负载条件下接口…

推荐一款新的自动化测试框架:DrissionPage

今天给大家推荐一款基于Python的网页自动化工具&#xff1a;DrissionPage。这款工具既能控制浏览器&#xff0c;也能收发数据包&#xff0c;甚至能把两者合而为一&#xff0c;简单来说&#xff1a;集合了WEB浏览器自动化的便利性和 requests 的高效率。 一、DrissionPage产生背…

vue3-element-admin搭建

vue3-element-admin 是基于 vue-element-admin 升级的 Vue3 Element Plus 版本的后台管理前端解决方案&#xff0c;是 有来技术团队 继 youlai-mall 全栈开源商城项目的又一开源力作功能清单技术栈清单技术栈 描述官网Vue3 渐进式 JavaScript 框架 https://v3.cn.vuejs.org/Ty…

经纬度坐标点和距离之间的转换

1.纬度相同&#xff0c;经度不同 在纬度相同的情况下&#xff1a; 经度每隔0.00001度&#xff0c;距离相差约1米&#xff1b; 每隔0.0001度&#xff0c;距离相差约10米&#xff1b; 每隔0.001度&#xff0c;距离相差约100米&#xff1b; 每隔0.01度&#xff0c;距离相差约1000米…

基于龙芯 2K1000 的嵌入式 Linux 系统移植和驱动程序设计(一)

2.1 需求分析 本课题以龙芯 2K1000 处理器为嵌入式系统的处理器&#xff0c;需要实现一个完成的嵌入式软件系统&#xff0c;系统能够正常启动并可以稳定运行嵌入式 Linux。设计网络设备驱 动&#xff0c;可以实现板卡与其他网络设备之间的网络连接和文件传输。设计 PCIE 设备驱…

我的 System Verilog 学习记录(1)

引言 技多不压身&#xff0c;准备开始学一些 System Verilog 的东西&#xff0c;充实一下自己&#xff0c;这个专栏的博客就记录学习、找资源的一个过程&#xff0c;希望可以给后来者一些借鉴吧&#xff0c;IC找工作的都加把油&#xff01; 本文是准备先简单介绍一下环境搭建…

洛谷P1125 [NOIP2008 提高组] 笨小猴 C语言/C++

[NOIP2008 提高组] 笨小猴 题目描述 笨小猴的词汇量很小&#xff0c;所以每次做英语选择题的时候都很头疼。但是他找到了一种方法&#xff0c;经试验证明&#xff0c;用这种方法去选择选项的时候选对的几率非常大&#xff01; 这种方法的具体描述如下&#xff1a;假设 maxn\…

JAVA集合之并发集合

从Java 5 开始&#xff0c;在java.util.concurrent 包下提供了大量支持高效并发访问的集合接口和实现类&#xff0c;如下图所示&#xff1a; 以CopyOnWrite开头的集合即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候&#xff0c;不直接往容器添加&#xff0c;而…

直播预告 | 嵌入式BI如何将数据分析真正融入业务流程

在信息化高速发展的今天&#xff0c;数据成为企业最有价值的资产之一。而数据本身很难直接传递有价值的信息&#xff0c;只有通过对数据进行挖掘、分析&#xff0c;才能让数据真正成为生产力。 商业智能&#xff08;BI&#xff09;应运而生&#xff0c;可以帮助企业更好地从数…

Julia 交互式命令窗口

执行 julia 命令可以直接进入交互式命令窗口&#xff1a; $ julia __ _ _(_)_ | Documentation: https://docs.julialang.org(_) | (_) (_) |_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.| | | | | | |/ _ | || |…

nginx的介绍及源码安装

文章目录前言一、nginx介绍二、nginx应用场合三、nginx的源码安装过程1.下载源码包2.安装依赖性-安装nginx-创建软连接-启动服务-关闭服务3.创建nginx服务启动脚本4.本实验---纯代码过程前言 高可用&#xff1a;高可用(High availability,缩写为 HA),是指系统无中断地执行其功…