当前位置: 首页 > news >正文

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

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申请空间的流程图

流程图

相关文章:

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

1.central cache的介绍 1.1框架思想 1.1.1哈希映射 centralcache其实也是哈希桶结构的&#xff0c;并且central cache和thread cacha的哈希映射关系是一致的。目的为了&#xff0c;当thread cache某一个哈希桶下没有内存块时&#xff0c;可以利用之前编写的SizeClass::Index…...

Spring Boot 整合 MyBatis 配置等案例教程

运行环境&#xff1a;JDK 7 或 8、Maven 3.0 技术栈&#xff1a;SpringBoot 1.5、SpringBoot Mybatis Starter 1.2 、MyBatis 3.4 前言 距离第一篇 Spring Boot 系列的博文 3 个月了。《Springboot 整合 Mybatis 的完整 Web 案例》第一篇出来是 XML 配置 SQL 的形式。虽然 XM…...

比特数据结构与算法(第三章_下)队列的概念和实现(力扣:225+232+622)

一、队列&#xff08;Queue&#xff09;队列的概念&#xff1a;① 队列只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表。② 入队列&#xff0c;进行插入操作的一端称为 队尾。出队列&#xff0c;进行删除操作的一端称为 队头。③ 队列中的元素…...

c++提高篇——STL容器实现打分系统

一、案例说明 有5名选手:选手ABCDE&#xff0c;10个评委分别对每一名选手打分&#xff0c;去除最高分&#xff0c;去除评委中最低分&#xff0c;取平均分。 二、案例实现 在实现这个系统时&#xff0c;我们规划一下实现的步骤以及细节&#xff1a; 1、创建一个选手类&#x…...

【图片上传记录三】element-ui组件详解与封装(自定义上传、限制文件大小、格式以及图片尺寸)

业务上有需求是前端上传 jpg/png/gif 格式, 并且 尺寸为 150px * 150px,300px*300px,428*428px 的图片 同时在上传的同时需要携带用户的个人信息以及其他额外信息 因此在 element-upload 基础之上 实现这个需求需要在上传前检查图片的大小&#xff0c;格式以及尺寸如何上传也成…...

一个golang版本管理工具

GitHub - moqsien/gvc: GVC is a productive tool to manage your dev environment for multi platforms and machines. | GVC 是一个用于快速配置和管理多机器跨平台的开发环境的生产力工具。 目前&#xff0c;gvc拥有以下功能或特点&#xff1a; go编译器自动安装和添加环…...

SpringBoot整合Spring Security过滤器链加载执行流程源码分析

文章目录1.引言2.Spring Security过滤器链加载1.2.注册名为 springSecurityFilterChain的过滤器2、查看 DelegatingFilterProxy类3.查看 FilterChainProxy类3.1 查看 doFilterInternal方法。3.2 查看 getFilters方法。4 查看 SecurityFilterChain接口5 查看 SpringBootWebSecur…...

Jest使用

一、测试到底测什么 提到测试的时候&#xff0c;即使是最简单的一个代码块可能都让初学者不知所措。最常问的问题的是“我怎么知道要测试什么&#xff1f;”。如果你正在写一个 Web 应用&#xff0c;那么你每个页面每个页面的测试用户交互的方式&#xff0c;就是一个很好的开端…...

定位于企业数字化底座,开箱可用(spring cloud+Vue)基础框架,赶紧收藏!

项目介绍&#xff1a;JVS是什么&#xff1f;JVS是企业级应用构建的基础脚手架&#xff0c;提供开箱即用的基础功能集成&#xff0c;其中集成了账户管理、租户管理、用户权限体系、三方登录、环境配置、各种业务日志等功能&#xff0c;还提供了对接低代码、数据中台的能力。JVS能…...

java字符统计

问题描述 给定一个只包含大写字母的字符串 &#xfffd; S, 请你输出其中出现次数最多的字符。 如果有多个字母均出现了最多次, 按字母表顺序依次输出所有这些字母。 输入格式 一个只包含大写字母的字符串 &#xfffd; S. 输出格式 若干个大写字母&#xff0c;代表答案。 …...

C#:Krypton控件使用方法详解(第八讲) ——kryptonBreadCrumb

今天介绍的Krypton控件中的kryptonBreadCrumb&#xff0c;下面开始介绍这个控件的属性&#xff1a;首先要介绍的是RootItem属性和外观属性&#xff1a;RootItem属性组中包含属性如下&#xff1a;image属性&#xff1a;代表在文字对象的前方插入一个图片&#xff0c;属性值如下图…...

2023从0开始学性能(1) —— 性能测试基础【持续更新】

背景 不知道各位大佬有没遇到上面的情况&#xff0c;性能这个东西到底是什么&#xff0c;还是以前的358原则吗&#xff1f;明显并不是适用于现在了。多次想踏入性能测试门槛都以失败告终&#xff0c;这次就以系列的方式来督促自己真正踏进性能测试的门槛。 什么是性能测试 通…...

如何通过一台 iPhone 申请一个 icloud 邮箱账号 后缀为 @icloud.com

总目录 iOS开发笔记目录 从一无所知到入门 文章目录需求关键步骤步骤后续需求 在 iPhone 自带的邮箱软件中添加账号&#xff0c;排第一位的是 iCloud 邮箱&#xff1a; 选 iCloud 之后&#xff1a; 提示信息是exampleicloud.com&#xff0c;也就是说是有icloud.com为域的邮箱…...

SQL89 计算总和

描述OrderItems表代表订单信息&#xff0c;包括字段&#xff1a;订单号order_num和item_price商品售出价格、quantity商品数量。order_numitem_pricequantitya110105a211100a21200a421121a5510a2119a775【问题】编写 SQL 语句&#xff0c;根据订单号聚合&#xff0c;返回订单总…...

Netty高级应用之:编解码器与群聊天室开发

Netty高级应用之&#xff1a;编解码器与群聊天室开发 文章目录Netty高级应用之&#xff1a;编解码器与群聊天室开发Netty编解码器Java的编解码Netty编解码器概念解码器(Decoder)编码器(Encoder)编码解码器CodecNetty案例-群聊天室聊天室服务端编写聊天室客户端编写Netty编解码器…...

Vue的生命周期

Vue的生命周期是指Vue实例从创建到销毁的过程&#xff0c;它包括了以下几个阶段&#xff1a;初始化、编译、挂载、更新、渲染和销毁。 初始化&#xff1a;Vue实例创建时&#xff0c;会执行初始化过程&#xff0c;主要包括以下几个步骤&#xff1a; 初始化数据&#xff1a;Vue…...

MySQL —— 数据库基础

文章目录1. centos7 安装Mysql2. 数据库的概念3. 数据库下创建库&#xff0c;表4. 库&#xff0c;表 的本质5. 数据库服务器 和 库 &#xff0c;表的关系6. MySQL架构7. 存储引擎前言&#xff1a; 数据库是对数据进行管理的软件。1. centos7 安装Mysql 需要把系统自带的MySQL给…...

多线程知识点

多线程 基本知识 创建线程的常用三种方式&#xff1a; 继承Thread类实现Runnable接口实现Callable接口&#xff08;JDK1.5>&#xff09; public class ThreadTest extends Thread {Overridepublic void run() {System.out.println(this.getName() "..开始.."…...

有序表之红黑树

文章目录1、五个条件2、调整策略2.1 插入调整的情况2.1.1 情况一&#xff1a;插入节点是红色&#xff0c;其父节点也是红色2.1.2 情况二2.1.2 代码实现2.2 删除调整的情况2.2.1 情况一&#xff1a;双重黑节点的兄弟节点也是黑色&#xff0c;且其兄弟的两个孩子也是黑色2.2.2 情…...

HTTP状态码都有哪些?

100&#xff1a;客户必须继续发出请求 101&#xff1a;客户要求服务器根据请求转换HTTP协议版本 二&#xff1a;2xx 200&#xff1a;交易成功 201&#xff1a;提示知道新文件的URL 202&#xff1a;接受和处理、但处理未完成 203&#xff1a;返回信息不确定或不完整 204&#…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...