六、高并发内存池--Central Cache

news2024/11/20 10:47:18

六、高并发内存池–Central Cache

6.1 Central Cache的工作原理

central cache也是一个哈希桶结构,他的哈希桶的映射关系跟thread cache是一样的。不同的是他的每个哈希桶位置挂是SpanList链表结构,不过每个映射桶下面的span中的大内存块被按映射关系切成了一个个小内存块对象挂在span的自由链表中。如果在thread cache中申请不到内存就会到central cache的同一个位置申请,thread cache和central cache的哈希桶的映射关系是完全一致的。

在这里插入图片描述
中心缓存central cache是如何工作的?

申请内存:

  1. 当thread cache中没有内存时,就会批量向central cache申请一些内存对象,这里的批量获取对象的数量使用了类似网络tcp协议拥塞控制的慢增长算法,具体细节参考代码实现;central cache也有一个哈希映射的spanlist,spanlist中挂着span,因为central cache在全局只有一个,所以从span中取出对象给thread cache,这个过程是需要加锁的,不过这里使用的是一个桶锁,而不是加一把大锁把整个central cache锁住,这是为什么呢?
    原因是central cache中哈希桶的映射关系和thread cache中哈希桶的映射关系是完全一致的,并且thread cache中哪个哈希桶的自由链表中没有内存对象,就会去central cache中相同位置的哈希桶中获取对象,不会到别的哈希桶获取对象的,因为别的哈希桶中span的自由链表的对象的大小是不一样的,所以我们只需要加上桶锁锁住当前位置的spanList即可,其它线程到central cache的其它位置的span中获取对象和我是不会相互影响的,所以加桶锁而不加大锁能够大大提高thread cache向central cache申请内存的效率。并且大多数情况线程在thread cache就能申请到内存,所以到central cache中申请内存对象的概率不会很高,因此桶锁的竞争也不会特别激烈,所以使用桶锁能尽可能提高效率。

  2. central cache映射的spanlist中所有span的都没有内存以后,则需要向page cache申请一个新的span对象,拿到span以后将span管理的内存按大小切好用自由链表链接到一起。然后从span中取对象给thread cache。

  3. central cache中挂的span中_useCount记录了该span分配了多少个对象出去,分配一个对象给threadcache,就++_useCcount。

释放内存:
当thread cache中的自由链表过长或者线程销毁,则会将内存释放回central cache中的,释放回来时–_useCount。当_useCount减到0时则表示所有对象都回到了span,则将span释放回page cache,page cache中会对前后相邻的空闲页进行合并,得到更大的页,缓解内存碎片(外碎片)问题。

由此可见,CentralCache之所以叫做中心缓存,是因为它做到了中轴调节内存分配的工作,不同的ThreadCache没有内存找我拿,我没有找PageCache拿,ThreadCache中内存释放过多后就还一些给我,我再分配给别的ThreadCache,我分配出去的span的小对象全部都还回来了又把span还回去给PageCache合成更大的span。

6.2 CentralCache.h

//因为central cache在整个进程中只有唯一的一个,所以可以设计成单例模式
class CentralCache
{
public:
	static CentralCache* GetInstance()
	{
		return &_sInst;
	}

	//从CentralCache中对应的哈希桶中获取一个不为空的Span大块内存的对象,size代表线程申请的小对象的大小
	Span* GetOneSpan(SpanList& list, size_t size);

	//获取多个obj,batchNum代表希望拿到多少个小对象,size代表线程申请的小对象的大小
	size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);

	//size是归还的小对象的大小
	void ReleaseListToSpans(void* start, size_t size);

private:
	//跟thread cache一样规模大小的哈希桶,每个桶都是一个挂满Span对象的链表
	//每个Span对象又是被切分成小块内存的自由链表
	SpanList _spanLists[NFREELIST];

	//单例模式需要把构造函数私有化
	CentralCache()
	{}

	//单例模式需要把拷贝构造函数删除,防拷贝
	CentralCache(const CentralCache&) = delete;

	//设置成静态对象就能保证只会创建出一个对象,保证全局只有一个唯一的CentralCache对象
	static CentralCache _sInst;

};


6.2 CentralCache.cpp


//静态对象需要在类内声明,类外定义
CentralCache CentralCache::_sInst;

//从CentralCache中对应的哈希桶中获取一个不为空的Span大块内存的对象
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	//遍历对应位置的哈希桶查找一个自由链表_freeList不为空的span
	Span* it = list.Begin();
	while (it != list.End())
	{
		if (it->_freeList != nullptr)
		{
			return it;
		}
		else
		{
			it = it->_next;
		}
	}


	//走到这里说明list遍历完了都没有找到一个有小对象的span,则需要向pagecache申请span
	//那么既然要向PageCache中获取span,那么CentralCache的桶锁就可以先解掉,为什么呢?
	//因为要去PageCache获取span对象的时候,CentralCache对应的桶可能有线程来归还span
	// 的小对象,如果没有解除桶锁的话,那么归还内存的线程就没有办法获取到桶锁,也就会被阻
	// 塞,因为我们以下的逻辑是向PageCache获取span,也就是说我们暂时不会操作CentralCache
	// 对应位置的哈希桶,所以可以先让别的线程获取到桶锁进行访问,这个过程是不会出现线程安
	//全的问题的,并且能够使不同的线程并行运行,能够提高内存池的效率

	//解掉桶锁
	list._mtx.unlock();

	//因为PageCache也是全局唯一的,并且这个NewSpan可能会是递归,所以我们不能在NewSpan内部
	//加锁,否则递归会出现死锁,所以我们在访问PageCache的时候需要先加一把大锁锁住PageCache,
	//为什么这里是加大锁锁住整个PageCache而不是在PageCache内部对应的哈希桶中加桶锁只锁住当前
	//的spanList呢?这里留个疑问,后面在PageCache的内部实现的时候再来解答
	PageCache::GetInstance()->_pageMtx.lock();
	Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
	span->_objSize = size;//记录span自由链表中每一个小内存块的大小,方便后续释放内存
	//span被分到CentralCache,说明span被使用了,置为true
	span->_isUse = true;
	PageCache::GetInstance()->_pageMtx.unlock();


	//提问:需要在这里立刻加上list的桶锁吗?
	//答案是:不用立刻加上桶锁,因为下面是对NewSpan切分的过程,这个过程中没有线程能够获取到
	//这个span,因为它还没有挂到哈希桶上,可以在切分完span之后,把它挂到对应的哈希桶的之前
	//再加上桶锁


	//需要把这个span大对象切分成一个一个的size大小的小对象挂在自由链表中
	// 
	//start是这个span对象的起始位置,可以通过span的页号换算过来
	char* start = (char*)(span->_pageId << PAGE_SHIFT);
	//bytes是这个span的大小,单位是字节,可以通过span页数换算
	size_t bytes = span->_n << PAGE_SHIFT;
	char* end = start + bytes;

	//先切分一个小对象给_freeList做头,方便尾插
	span->_freeList = start;
	void* tail = start;

	//从下一个小对象开始尾插
	start += size;


	//尾插
	while (start < end)
	{

		NextObj(tail) = start;
		tail = NextObj(tail);
		start += size;

	}

	//最后记得把tail的前4个或者8个字节的内容置空,否则里面的随机值会导致这个span对象的
	//自由链表的尾部越界访问了一些内存,最后会导致程序崩溃
	NextObj(tail) = nullptr;

	//以下是把切分好的span挂到对应的哈希桶中,所以需要重新加上桶锁,防止出现线程安全的问题
	list._mtx.lock();

	//获取到的span头插到对应位置的哈希桶
	list.PushFront(span);

	return span;
}

//获取多个obj
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	//计算映射的哈希桶的下标
	size_t index = SizeClass::Index(size);

	//以下是获取span和从span中获取小对象,需要加上桶锁
	_spanLists[index]._mtx.lock();

	//获取一个不为空的Span,获取到的span是切分好小对象并连接到自由链表中的span
	Span* span = GetOneSpan(_spanLists[index], size);
	assert(span);
	assert(span->_freeList);

	//取span对象的自由链表的batchNum个小对象,但是不一定能取到batchNum个
	//可能不够,所以要注意end走到nullptr,这一块需要画图理解,一定画图
	start = span->_freeList;
	end = start;
	int actualNum = 1;//因为下面循环走了batchNum-1步,所以actualNum应该从1开始(方便把end->_next置空)
	int i = 0;
	while (i < batchNum - 1 && NextObj(end)!=nullptr)
	{
		end = NextObj(end);
		actualNum++;
		i++;
	}
	span->_freeList = NextObj(end);
	NextObj(end) = nullptr;//一定要注意置空

	//这个span被拿走了几个小对象,_useCount就要+=几,方便后面小对象全部释放
	//回来了的时候可以把这个span还会给PageCache
	span->_useCount += actualNum;


	_spanLists[index]._mtx.unlock();

	return actualNum;

}

void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	//计算归还的小块内存在CentralCache中哪一个下标对应的哈希桶中
	size_t index = SizeClass::Index(size);

	//访问CentralCache中的index下标对应的哈希桶,需要加上桶锁
	_spanLists[index]._mtx.lock();

	//循环直到start链表为空,把对应的小对象都头插到对应的span中
	while (start != nullptr)
	{
		void* next = NextObj(start);

		//通过start地址的值可以转换成页号,进而通过idMapSpan找到start属于哪一个span
		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		//把start对应的内存块头插到对应的span的自由链表中
		NextObj(start) = span->_freeList;
		span->_freeList = start;
		span->_useCount--;

		//如果span的_useCount减到零,说明从span切分出去的所有的小对象已经全部还回来了
		//可以进一步把这个span还给PageCache,以便PageCache合并前后空闲页
		if (span->_useCount == 0)
		{
			//要把span还回去给PageCache,所以要把span从_spanLists[index]的哈希桶中删除掉
			_spanLists[index].Erase(span);
			span->_freeList = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;

			//既然要把span还给PageCache,那么就暂时不会访问_spanLists[index]哈希桶了
			//可以先把桶锁解掉,这样别的线程就能访问_spanLists[index]对应的哈希桶了
			_spanLists[index]._mtx.unlock();

			//访问PageCache需要加上大锁,防止出现线程安全的问题
			PageCache::GetInstance()->_pageMtx.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMtx.unlock();

			//要重新上锁,因为循环不一定走完了,要继续访问CentralCache对应的桶
			//归还小对象给对应的span
			_spanLists[index]._mtx.lock();
		}

		start = next;

	}

	_spanLists[index]._mtx.unlock();

}


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

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

相关文章

pycharm创建的虚拟环境为什么用conda env list命令查询不到?

问题描述&#xff1a;pycharm创建的虚拟环境为什么用conda env list命令查询不到。 pycharm开发环境可以创建虚拟环境&#xff0c;目的是为隔绝其他环境种库带来的版本干扰&#xff0c;但是发现一个问题&#xff0c;无论是在windows终端、anaconda终端、Pycharm开发环境中的终…

Redis之MoreKey问题及Scan命令解读

目录 MoreKey问题讨论 Scan命令 Sscan命令 Hscan命令 Zscan命令 MoreKey问题讨论 keys * 查看当前库所有key 对于海量数据执行key *会造成严重服务卡顿、影响业务。在实际环境中最好不要使用。生产制造过程中keys * / flushdb/flushall等危险命令以防止误删误用。 大量的…

求臻医学:ctDNA动态监测可预测A+T治疗疗效

臻知识专家访谈 | 第45期 抗血管生成药物贝伐珠单抗&#xff08;A&#xff09;和一代靶向药厄洛替尼&#xff08;T&#xff09;的联合&#xff0c;能够显著提高EGFR敏感突变&#xff08;非T790M&#xff09;NSCLC患者的PFS和5年生存率&#xff0c;延缓靶向药物的耐药。但不是所…

智慧排水监测系统,科技助力城市排水治理

城市里&#xff0c;人们每天通过道路通行&#xff0c;人多&#xff0c;路窄&#xff0c;都会拥堵。同样&#xff0c;下雨天&#xff0c;雨水通过雨篦汇集、管道输送&#xff0c;最终排出去&#xff0c;当雨水过大&#xff0c;或者管道过窄&#xff0c;或者管道不通畅&#xff0…

考研408 | 【操作系统】终章

I/O设备的基本概念和分类 I/O设备&#xff1a; I/O设备的分类 1.按使用特性&#xff1a; 2.按传输速率分类&#xff1a; 3.按信息交换的单位分类&#xff1a; 总结&#xff1a; I/O控制器 I/O设备的机械部件&#xff1a; I/O设备的电子部件&#xff08;I/O控制器&#…

Stable Diffusion 从入门到企业级实战0401

一、概述 本章是《Stable Diffusion 从入门到企业级实战》系列的第四部分能力进阶篇《Stable Diffusion ControlNet v1.1 图像精准控制》第01节&#xff0c; 利用Stable Diffusion ControlNet Inpaint模型精准控制图像生成。本部分内容&#xff0c;位于整个Stable Diffusion生…

Python 实现单例模式的五种写法!

单例模式&#xff08;Singleton Pattern&#xff09; 是一种常用的软件设计模式&#xff0c;该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中&#xff0c;某个类只能出现一个实例时&#xff0c;单例对象就能派上用场。 比如&#xff0c;某个服务器程序的…

DEAP库文档教程一

DEAP是一个新的用于快速验证和测试新想法的演化计算框架。它致力于直接地构建算法和数据结构的简单化。它可以很好地应用在并行机制中。下面的文档将会展示许多关键概念以及构建你自己的演化算法时的一些特征。 第一步 1、总览(从这里开始) 2、安装 3、如何进入端口?(porting…

微服务设计和高并发实践

文章目录 1、微服务的设计原则1.1、服务拆分方法1.2、微服务的设计原则1.3、微服务架构 2、高并发系统的一些优化经验2.1、提高性能2.1.1、数据库优化2.1.2、使用缓存2.1.3、服务调用优化2.1.4、动静分离2.1.5、数据库读写分离 2.2、服务高可用2.2.1、限流和服务降级2.2.2、隔离…

Axure RP美容美妆医美行业上门服务交互原型图模板源文件

Axure RP美容美妆医美行业上门服务交互原型图模板源文件&#xff0c;原型内容属于电商APP&#xff0c;区别于一般电商&#xff0c;它的内容是‘美容美发美妆等’上门服务等。大致流程是线上买单&#xff0c;线下实体店核销消费。 附上预览演示&#xff1a;axure9.com/mobile/73…

DEAP库文档教程五----计算统计

本小结将重点围绕模型在计算统计方面的问题&#xff0c;进行详细的论述 1、Computing Statistics 通常情况下&#xff0c;我们想要在优化过程中编辑数据。Statistic模块可以在任何设计好的目标上改变一些本不可改变的数据。为了达到这个目的&#xff0c;需要使用与工具箱中完…

2023-9-2 Prim算法求最小生成树

题目链接&#xff1a;Prim算法求最小生成树 #include <iostream> #include <cstring> #include <algorithm>using namespace std;const int N 510, INF 0x3f3f3f3f;int n, m; int g[N][N]; int dist[N]; bool st[N];int prim() {memset(dist, 0x3f, size…

一、项目介绍 二、什么是内存池?

目录 一、项目介绍这个项目是做什么的&#xff1f; 二、什么是内存池&#xff1f;2.1 什么是池化技术&#xff1f;2.2 内存池2.3 内存池主要解决什么问题&#xff1f;2.4 malloc 一、项目介绍 这个项目是做什么的&#xff1f; 当前项目是实现一个高并发的内存池&#xff0c;它…

时序预测 | MATLAB实现CNN-BiGRU卷积双向门控循环单元时间序列预测

时序预测 | MATLAB实现CNN-BiGRU卷积双向门控循环单元时间序列预测 目录 时序预测 | MATLAB实现CNN-BiGRU卷积双向门控循环单元时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.MATLAB实现CNN-BiGRU卷积双向门控循环单元时间序列预测&#xff1b; 2.运行环境…

lv3 嵌入式开发-2 linux软件包管理

目录 1 软件包管理 1.1流行的软件包管理机制 1.2软件包的类型 1.3软件包的命名 2 在线软件包管理 2.1APT工作原理 2.2更新软件源 2.3APT相关命令 3 离线软件包管理 1 软件包管理 1.1流行的软件包管理机制 Debian Linux首先提出“软件包”的管理机制---Deb软件包 …

【python爬虫】10.指挥浏览器自动工作(selenium)

文章目录 前言selenium是什么怎么用设置浏览器引擎获取数据解析与提取数据自动操作浏览器 实操运用确认目标分析过程代码实现 本关总结 前言 上一关&#xff0c;我们认识了cookies和session。 分别学习了它们的用法&#xff0c;以及区别。 还做了一个项目&#xff1a;带着小…

酒店报修系统应该怎么选?“的修”报修管理软件有什么优势?

随着酒店行业的快速发展&#xff0c;酒店之间的竞争也日益激烈。在竞争激烈的市场中&#xff0c;酒店的设备和设施的维修与管理对于酒店的运营和管理至关重要。一旦设备和设施出现故障&#xff0c;不仅会影响酒店的正常运营和服务质量&#xff0c;还会对酒店的声誉和经济效益造…

Linux 操作系统实战视频课 - GPIO 基础介绍

文章目录 一、GPIO 概念说明二、视频讲解沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解 GPIO 。 一、GPIO 概念说明 ARM 平台中的 GPIO(通用输入/输出)是用于与外部设备进行数字输入和输出通信的重要硬件接口。ARM 平台的 GPIO 特性可以根据具体的芯…

8月《中国数据库行业分析报告》已发布,聚焦数据仓库、首发【全球数据仓库产业图谱】

为了帮助大家及时了解中国数据库行业发展现状、梳理当前数据库市场环境和产品生态等情况&#xff0c;从2022年4月起&#xff0c;墨天轮社区行业分析研究团队出品将持续每月为大家推出最新《中国数据库行业分析报告》&#xff0c;持续传播数据技术知识、努力促进技术创新与行业生…

多因素认证与身份验证:分析不同类型的多因素认证方法,介绍如何在访问控制中使用身份验证以增强安全性

随着数字化时代的到来&#xff0c;信息安全问题变得愈发重要。在网络世界中&#xff0c;用户的身份往往是保护敏感数据和系统免受未经授权访问的第一道防线。单一的密码已经不再足够&#xff0c;多因素认证&#xff08;MFA&#xff09;应运而生&#xff0c;成为提升身份验证安全…