【C++项目】高并发内存池第二讲中心缓存CentralCache框架+核心实现

news2024/11/24 10:59:40

请添加图片描述

CentralCache

  • 1.框架介绍
  • 2.核心功能
  • 3.核心函数实现+介绍
    • 3.1Span+SpanList介绍
    • 3.2CentralCache.h
    • 3.3CentralCache.cpp
    • 3.4TreadCache申请内存函数介绍
    • 3.5慢反馈算法

1.框架介绍

回顾一下ThreadCache的设计:
0
如图所示,ThreadCache设计是一个哈希桶结构,每一个桶挂的是一块切分好的小块内存块,每个线程独享一个ThreadCache。

CentralCache:
1

CentralCache也是一个哈希桶结构,跟ThreadCache的结构类似,只不过ThreadCache挂的是切分好的小对象内存块,而CentralCache挂的是一个spanlist 是一个连续的大块内存链表,链接着很多个span(大块内存)

而且根据下标映射的位置,切分好不同大小的对象,如第一个桶挂着8kb的小对象span,最后一个桶挂着大一些的 256kb的span,对象越小,span对象越多,反之。

2.核心功能

CentralCache作为中心调度,需要实现核心的内存分配调度工作,包括:

  1. 当ThreadCache内存不足时要向CentralCache申请,当CentralCache内存不足时再递进地向PageCache申请
  2. 当ThreadCache内存不用时,需要回收回来,再回收给PageCache,重新拼接成大块内存,解决内存碎片化问题
  3. 锁:因为涉及多个线程会访问同一个桶,所以要加锁实现 这里用到地是一个桶锁
    - . 使用单例模式好处:

全局访问点:单例模式确保只有一个实例,并提供了一个全局的访问点,这样你可以在项目的任何地方访问 ‘CentralCache’ 的唯一实例。这对于管理和共享某些全局资源非常有用。

. 节省内存和初始化时间:饿汉模式确保 ‘CentralCache’ 在应用程序启动时创建,因此不需要等到实际需要它时再创建。这可以节省内存,并且初始化时间会更快,因为对象已经准备好。

线程安全:饿汉模式的单例在初始化时就创建了一个实例,因此它不需要考虑多线程竞争的问题。多线程环境下,多个线程访问单例时,它们都会引用相同的实例,而不会创建多个实例,因此不会导致竞争条件。

更容易管理:单例模式将全局状态和操作封装到一个类中,使代码更有组织性,易于维护和扩展。你可以通过单一的访问点执行与 ‘CentralCache’ 相关的操作。

. 有效资源管理:‘CentralCache’ 在内存池中起到了关键作用,以有效地分配和回收内存。它的唯一实例确保资源的一致性和高效的内存管理。

总之,使用单例模式可以更轻松地管理和访问 ‘CentralCache’ 的唯一实例,确保全局一致性和线程安全,节省内存和初始化时间,并使代码更具可维护性。这对于高并发内存池的实现非常有帮助。

3.核心函数实现+介绍

3.1Span+SpanList介绍

  1. 首先是结构体Span的介绍:
//Span:管理多个连续页的大块内存跨度结构
struct Span
{
	PAGE_ID _page_id = 0;//大块内存的起始页的页号

	size_t _n = 0;//页的数量
	Span* _next = nullptr; //设计成双向链表结构
	Span* _prev = nullptr;

	size_t _objSize = 0;//切好的小对象的大小
	size_t _usecount = 0;//切好的小块内存,被分配给threadcache的计数
	void* _freeList = nullptr;

	bool _isUse = false;//是否在使用 涉及到多个线程同时访问一个span 会有线程安全问题
};
  1. Spanlist代码
class  SpanList
{
public:
	SpanList()
	{
		_head = new Span;
		_head->_next = _head;
		_head->_prev = _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;
		delete pos;
	}
	Span* Begin()
	{
		return _head->_next;
	}
	Span* End()
	{
		return _head;
	}
	bool Empty()
	{
		return _head->_next == _head;
	}
	
	void PushFront(Span* pos)
	{
		Insert(Begin(),pos);
	}
	//头删
	Span* PopFront()
	{
		Span* front = _head->_next;
		Erase(front);
		return front;
	}

public:
	std::mutex _mtx;//桶锁
private:
	Span* _head;
	
};

以上都是一些基础的数据结构知识,不过多介绍。

3.2CentralCache.h

#pragma once
#include"Common.h"
//单例模式 --->饿汉模式 
class CentralCache
{
public:
	static CentralCache* GetInstance() //获取单例
	{
		return &_Istance;
	}

	//获取一个非空的span
	Span* GetOneSpan(SpanList& list, size_t size);//定义 .CPP实现

	//从缓冲中心获取一定数量的对象返回给treadCache
	size_t FetchRangeObj(void*& star, void*& end, size_t batchNum,size_t size);//Fetch-->获取

	//将一定数量的对象释放到span跨度
	void ReleaseListToSpans(void* start, size_t size);  //Release-->释放
private:
	SpanList _spanlists[NFREELISTS];

	//确保类 只创建一个实例
	CentralCache() //构造函数私有化 
	{}
	CentralCache(const CentralCache&) = delete;//禁掉拷贝构造 
	static CentralCache _Istance;//首次调用即创建唯一单例 
};
Span* GetOneSpance(SpanList& list, size_t size);

3.3CentralCache.cpp

#pragma once
#include"CentralCache.h"
#include"PageCache.h"
CentralCache CentralCache::_Istance;
size_t CentralCache::FetchRangeObj(void*& star, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);//计算出桶的下标
	_spanlists[index]._mtx.lock();//加锁
	Span* span = GetOneSpance(_spanlists[index], size);

	assert(span);
	assert(span->_freeList);//断言成功 则证明至少有一个块

	//从span中获取batchNum个对象 
	//如果实际的个数不够 那就有多少拿多少 这里就需要有一个实际变量actuall作为返回
	star = span->_freeList;
	end = star;

	size_t i = 0;
	size_t actualNum = 1;
	while (i < batchNum - 1 && NextObj(end) != nullptr)
	{
		//更新end的位置
		end = NextObj(end);
		actualNum++;
		i++;
	}
	span->_freeList = NextObj(end);
	NextObj(end) = nullptr;
	span->_usecount += actualNum;

	//条件断点
	void* cur = star;
	int koko = 0;
	while (cur)
	{
		cur = NextObj(cur);
		koko++;
	}
	if (koko != actualNum)
	{
		int x = 0;
	}
	_spanlists[index]._mtx.unlock();
	return actualNum;
}

Span* GetOneSpance(SpanList& list, size_t size)
{
	//查看一下当前spanlists是否span未分配的
	Span* it = list.Begin();
	while (it != list.End())
	{
		if (it->_freeList!=nullptr)
		{
			return it;
		}
		else
		{
			it = it->_next;
		}
	}

	//先把centralCache的桶解掉 ,这样如果其他的线程释放对象回来,不会阻塞

	list._mtx.unlock();

	//走到这里说明没有空闲的span了,再往下找PageCache要
	
	PageCache::GetInstance()->_pageMtx.lock(); //加锁 这是一个大锁

	Span* span = PageCache::Newspan(SizeClass::NumMovePage(size));

	span->_isUse = true;
	span->_objSize = size;
	PageCache::GetInstance()->_pageMtx.unlock();//到这一步程序就已经申请到一个span了

	//对span进行切分 此过程不需要加锁 因为其他的线程访问不到这个span

	//通过页号 计算出起始页的地址 add=_pageID<<PAGE_SHIFT
	//计算span的大块内存的起始地址 和大块内存的大小(字节数)

	char* start = (char*)(span->_page_id << PAGE_SHIFT);
	size_t bytes = span->_n << PAGE_SHIFT;
	char* end = start + bytes;

	//把大块内存切成自由链表 链接起来
	//这里使用尾插 因为尾插会保持内存空间的连续性 提高CPU的缓存利用率

	span->_freeList = start;
	start += size;
	void* tail = span->_freeList;
	int i = 1;
	while (start < end)
	{
		++i;
		NextObj(tail) = start;
		tail = NextObj(tail);
		start += size;
	}
	if (tail == nullptr)
	{
		int x = 0;
	}
	NextObj(tail) = nullptr;

	void* cur = span->_freeList;
	int koko=0;
	//条件断点 
	//类似死循环 可以让程序中断 程序会在运行的地方停下来

	while (cur)
	{
		cur = NextObj(cur);
		koko++;
	}
	if (koko != (bytes / 16))
	{
		int x = 0;
	}
	//这里切好span以后 需要把span挂到桶里面 同时加锁

	list._mtx.lock();
	list.PushFront(span);
	list._mtx.unlock();

	return span;
}

//回收内存
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanlists[index]._mtx.lock();
	while (start)
	{
		void* next = NextObj(start);

		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		NextObj(start) = span->_freeList;
		span->_freeList = start;
		span->_usecount--;
		if (span->_usecount == 0)
			//说明span切分出去的内存小块都回收回来了,
			//这时这个span就可以再回收给page cache,page cache可以再尝试去做前后页的合并
		{
			_spanlists[index].Erase(span);
			span->_freeList = nullptr;
			span->_prev = nullptr;
			span->_next = nullptr;

			//释放span给page cache时,使用page cache的锁就可以了
			//所以需要先把桶锁解掉再加page cache的大锁
			_spanlists[index]._mtx.unlock();
			PageCache::GetInstance()->_pageMtx.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMtx.unlock();
			_spanlists[index]._mtx.lock();

		}
		start = next;
	}
	_spanlists[index]._mtx.unlock();
}

3.4TreadCache申请内存函数介绍

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include"ThreadCache.h"
#include<algorithm>
#include"CentralCache.h"
void* ThreadCache::Allocate(size_t size)
{
	assert(size <= MAX_BYTES);
	size_t alignSize = SizeClass::RoundUp(size);
	size_t index = SizeClass::Index(size);//计算哈希桶的下标

	if (!_freeLists[index].Empty())
	{
		return _freeLists[index].Pop();
	}
	else
	{
		return FetchFromCentralCache(index, alignSize);
	}
}

void ThreadCache::Deallocate(void* ptr, size_t size)
{
	assert(ptr);
	assert(size <= MAX_BYTES);
	//找到对映射的自由链表桶 插入

	size_t index = SizeClass::Index(size);
	_freeLists[index].Push(ptr);

	//当链表的长度大于一次批量申请的内存就开始归还一段给CentralCache
	if (_freeLists[index].Size() >= _freeLists[index].MaxSize())
	{
		ListTooLong(_freeLists[index], size);//回收内存给CentralCache
	}
}
void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
	void* start = nullptr;
	void* end = nullptr;
	list.PopRang(start, end, list.MaxSize());
	CentralCache::GetInstance()->ReleaseListToSpans(start, size);
}

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
	//慢开始反馈调节算法(batch:批量)
	//1.最开始不会一次向central cache要太多,因为要多了可能用不完,浪费
	//2.如果你不要这个size大小内存需求,那么batchNum就会不断增长,直到上限
	//3.size越大,一次向central cache要的batchNum就越小
	//4.size越小,一次向central cache要的batchNum就越大
	size_t batchNum = min(_freeLists[index].MaxSize(), SizeClass::NumMoveSize(size));

	if (_freeLists[index].MaxSize() == batchNum)
	{
		_freeLists[index].MaxSize() += 1;
	}

	void* start = nullptr;
	void* end = nullptr;
	size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, size);
	assert(actualNum >= 1);
	if (actualNum == 1)
	{
		assert(start == end);
		return start;
	}
	else
	{
		_freeLists[index].PushRange(NextObj(start), end, actualNum - 1);
		return start;
	}
}

3.5慢反馈算法

申请的结构涉及 这里主要用的是慢反馈算法
000
00
0
这里用双重机制来控制申请模块,第一次申请最大的申请数maxSize=1;然后计算慢启动函数的值,去最小的一个,如果说取的值是maxSize,那么maxSize就+=1;慢慢增长,这里可以根据实际需求调整增长的速度。
如果最后增长的范围超过慢启动设置的阈值,就取慢启动设置的值,在这两者策略下申请内存的机制得到更大的优化,大程度避免一次申请过大导致内存碎片问题。请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

万界星空科技/免费MES系统/开源MES/免费追溯管理

开源系统概述&#xff1a; 万界星空科技免费MES、开源MES、商业开源MES、市面上最好的开源MES、MES源代码、免费MES、免费智能制造系统、免费排产系统、免费排班系统、免费质检系统、免费生产计划系统、免费仓库管理系统、免费出入库管理系统、免费可视化数字大屏。 万界星空…

CMake学习(一):使用CMake构建一个简单的C++项目

文章目录 一. 构建一个简单的项目二. 构建过程1. 创建程序源文件2. 编写CMakeList.txt文件3. 构建项目并编译源代码 附件 一. 构建一个简单的项目 最基本的CMake项目是从单个源代码文件构建的可执行文件。对于像这样的简单项目&#xff0c;只需要一个包含三个命令的CMakeLists…

QT的Qporcess功能的使用

具体实现代码如下&#xff1a; #include <QProgressBar>//必须要包含的头文件 #include <QProcess>// 创建一个QProgressBar对象QProgressBar *progressBar new QProgressBar(this);QProcess *proces;process_shownew process;// 设置进度条的最小值和最大值prog…

YOLOv5论文作图教程(1)— 软件介绍及下载安装(包括软件包+下载安装详细步骤)

前言:Hello大家好,我是小哥谈。在学习YOLOv5算法的过程中,很多同学都有发表论文的需求。作为文章内容的支撑,图表是最直接的整合数据的工具,能够更清晰地反映出研究对象的结果、流程或趋势。在发表论文的时候,审稿人除了关注论文的内容和排版外,也会审核图表是否清晰美观…

按摩 推拿上门服务小程序源码 家政上门服务系统源码

按摩 推拿上门服务小程序源码 家政上门服务系统源码 上门服务系统是一款基于互联网和移动应用的高端家政服务预订平台&#xff0c;它集成了用户、服务员、客户三方的需求于一体&#xff0c;为广大市民提供方便、高效、安全、舒适的家居服务体验&#xff0c;让你在家当皇帝&…

征战EDU证书站

1.前言&#xff1a; 挖了一段时间EDU老破小的站&#xff0c;也该拿证书站下手了。下手的第一个目标&#xff0c;那必然是漏洞排行榜第一的某交大&#xff01;&#xff01;&#xff01; 2.信息搜集 想快速挖到漏洞&#xff0c;必须信息搜集全面。如果信息搜集不到位不全面&…

YOLO V8语义分割模型部署

目录 1 配置pytorch环境 2 配置yolo环境 3 测试yoloV8的语义分割模型 1 配置pytorch环境 我的电脑为Y9000P 4090&#xff0c;英伟达显卡驱动版本为525.105.17&#xff0c;驱动显示最高的cuda版本号为12.0&#xff0c;cuda版本为11.6&#xff0c;cudnn版本号为8.5.0。Anaconda…

如何将音频与视频分离

您一定经历过这样的情况&#xff1a;当你非常喜欢视频中的背景音乐时&#xff0c;希望将音频从视频中分离出来&#xff0c;以便你可以在音乐播放器中收听音乐。有没有一种有效的方法可以帮助您快速从视频中提取音频呢&#xff1f;当然是有的啦&#xff0c;在下面的文章中&#…

Windows命令行窗口修改字体方法

问题&#xff1a;嫌弃原来的字体太丑&#xff0c;不好看&#xff0c;影响心情 长得丑还天天在我眼前晃悠&#xff1a; 改一改字体 输入命令&#xff1a; chcp 437 把字体换成consola 学习的心情都变好了

美国亚马逊UL60335认证怎么办理,费用是多少

UL60335认证是由美国安全实验室&#xff08;UnderwritersLaboratories&#xff09;颁发的&#xff0c;它对各类家用电器进行严格的测试和认证&#xff0c;确保其在正常使用情况下不会给消费者带来任何伤害。 本文将从不同的角度来叙述亚马逊UL60335认证的重要性和成败因素。 1.…

Kafka快速入门(最新版3.6.0)

文章目录 一、初识MQ1.1 什么是MQ1.2 同步和异步通讯1.1.1 同步通讯1.1.2 异步通讯 1.3 技术对比1.4 MQ的两种模式 二、初识Kafka2.1 Kafka的使用场景2.2 Kafka基本概念2.3 Topic与Partition 三、Kafka基本使用3.1 部署前的准备3.2 启动kafka服务器3.3 Kafka核心概念之Topic3.4…

laravel队列

laravel redis队列 1、创建job队列任务 php artisan make:job StoreUser执行上述命令后&#xff0c;会生成app/Jobs/StoreUser.php文件&#xff0c;编辑文件内容如下&#xff1a; <?phpnamespace App\Jobs;use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queu…

JavaScript基础知识(二)

JavaScript基础知识&#xff08;二&#xff09; 一、ES2015 基础语法1.变量2.常量3.模板字符串4.结构赋值 二、函数进阶1. 设置默认参数值2. 立即执行函数3. 闭包4. 箭头函数 三、面向对象1.面向对象概述2.基本概念3.新语法与旧语法4.ES5 面向对象的知识4.1 ES5构造函数4.2 原型…

一秒开挂!工厂模式让你告别重复代码!

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、工…

大模型开发07:LangChain 开发基础

大模型开发07:LangChain 开发基础 一、Model I/O 输入输出 任何语言模型应用程序的核心元素是什么?LangChain 提供了与任何语言模型交互的构建块。 Prompts: 模板化、动态选择和管理模型输入Language models: 通过通用的接口访问大模型Output parsers: 大模型输出解析模板化…

Oracle Enterprise Manager 认证列表

认证列表的查找方法参见Accessing the Enterprise Manager Certification Matrix。 认证分为对于OMS的&#xff0c;和对于Agent的。 OMS和Agent的描述见这里。 Oracle Management Agent The Management Agent is an integral software component that enables you to conver…

开源微信小程序商城源码PHP带后台管理——构建高效电商平台的基石

随着移动互联网的快速发展&#xff0c;微信小程序作为一种新型的电商形态&#xff0c;正逐渐成为商业领域的一种重要力量。对于许多商家来说&#xff0c;拥有一个功能完善、安全可靠的小程序商城源码至关重要。分享一款开源的微信小程序商城源码PHP&#xff0c;春哥七合一DIY小…

协作机器人应用场景

UR 双臂协作机器人在有人环境中 作业 ABB 双臂协作机器人进行医疗康复作业 KUKA iiwa 协作机器人拖动示教作业 机器人 – 环境物理交互控制要 求机器人具有力感知能力&#xff01;&#xff01; 在传 统的操作任务中&#xff0c;机械臂末端的静态 接触力可以通过多维力传感器…

【什么是高斯分布?还有什么分布?他们的用途是什么?】

高斯分布 高斯分布,也被称为正态分布或钟形曲线,是统计学中最为常见和重要的连续概率分布之一。高斯分布的概率密度函数(PDF)是: 其中: ( x ) 是变量( \mu ) 是分布的均值(mean)( \sigma ) 是标准差(standard deviation)( \sigma^2 ) 是方差(variance)以下是关于高…

#1024程序员节# 大家一起努力

1024&#xff0c;一年一度的程序员节&#xff0c;俗称“爱码士节”。 1024是2的十次方&#xff0c;二进制计数的基本计量单位之一&#xff0c;1G1024M&#xff0c;而1G与1级谐音&#xff0c;也是一级棒的意思~。程序员(英文Programmer)是从事程序开发、维护的专业人员。 程序员…