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

六、高并发内存池–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();

}


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

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

相关文章

IPV4地址说明

设想一个场景&#xff1a; 你有两台电脑A和B&#xff0c;需要把A的数据传输到B&#xff0c;怎么办&#xff1f; 1 我们可以用U盘进行拷贝&#xff0c;就是把A的数据拷贝到B 2 我们可以用一根网线把AB连接起来 显然&#xff0c;两台电脑用一根网线。那要是n台电脑呢&#xff1f;…

进程管理死死的学

进程管理 文件属性 chattr【扩展】 chattr chattr i 文件名 # 添加权限 a 可追加&#xff0c;不可修改 i 只可查看 A 不修改访问时间 charrt -i 文件名 # 取消权限 -R 递归处理&#xff0c;将指令目录下的所有文件及子目录一并处理&#xff1b;lsattr 查看文件属性 lsattr …

事务的总结

数据库事务 数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行&#xff0c;要么完全不执行。事务管理是一个重要组成部分&#xff0c;RDBMS 面向企业应用程序&#xff0c;以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性描述…

js对中文进行base64编码和解码操作,解决中文乱码问题

我使用github api的接口获取文件内容&#xff0c;然后使用atob进行解码&#xff0c;但是发现&#xff1a;乱码.......糟心啊 所以就有了我封装的方法&#xff1a; export const encode64 (str) > {// 首先&#xff0c;我们使用 encodeURIComponent 来获得百分比编码的UTF…

Python数据分析案例30——中国高票房电影分析(爬虫获取数据及分析可视化全流程)

案例背景 最近总看到《消失的她》票房多少多少&#xff0c;《孤注一掷》票房又破了多少多少..... 于是我就想自己爬虫一下获取中国高票房的电影数据&#xff0c;然后分析一下。 数据来源于淘票票&#xff1a;影片总票房排行榜 (maoyan.com) 爬它就行。 代码实现 首先爬虫获…

Django传递dataframe对象到前端网页

在django前端页面上展示的数据&#xff0c;还是使用django模板自带的语法 方式1 不推荐使用 直接使用 【df.to_html(indexFalse)】 使用to_html他会生成一个最基本的表格没有任何的样式&#xff0c;一点都不好看&#xff0c;如果有需要的话可以自行修改表格的样式&#xff0c;…

【教程】部署apprtc服务中安装google-cloud-cli组件的问题及解决

#0# 前置条件 已经安装完成node&#xff0c;grunt&#xff0c;node 组件和python pip包等。需要安装google-cloud-cli组件。 Ubuntu安装google-cloud-cli组件 apprtc项目运行需要google-cloud-cli前置组件&#xff0c;且运行其中的dev_appserver.py。 根据google官方的关于安…

如何使用CSS实现一个自适应等高布局?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用 Flexbox 布局⭐ 使用 Grid 布局⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发…

Linux(实操篇三)

Linux实操篇 Linux(实操篇三)1. 常用基本命令1.7 搜索查找类1.7.1 find查找文件或目录1.7.2 locate快速定位文件路径1.7.3 grep过滤查找及"|"管道符 1.8 压缩和解压类1.8.1 gzip/gunzip压缩1.8.2 zip/unzip压缩1.8.3 tar打包 1.9 磁盘查看和分区类1.9.1 du查看文件和…

RT_Thread内核机制学习(六)信号量

要传输较大数据时&#xff0c;使用队列。 传输较小数值时&#xff0c;使用邮箱。 队列、邮箱用来传递数据。 如果只是用来传递资源的个数&#xff0c;可以使用信号量。 A车与B车只需要传递信号量&#xff08;代表资源&#xff09;。 信号量 获取信号量 如果value>0&…

AI工人操作行为流程规范识别算法

AI工人操作行为流程规范识别算法通过yolov7python网络模型框架&#xff0c;AI工人操作行为流程规范识别算法对作业人员的操作行为进行实时分析&#xff0c;根据设定算法规则判断操作行为是否符合作业标准规定的SOP流程。Yolo意思是You Only Look Once&#xff0c;它并没有真正的…

记1次前端性能优化之CPU使用率

碰到这样的一个问题&#xff0c;用户反馈页面的图表一直加载不出来&#xff0c;页面还卡死 打开链接页面&#xff0c;打开控制台 Network 看到有个请求一直pending&#xff0c;结合用户描述&#xff0c;页面一直loading,似乎验证了我的怀疑&#xff1a;后端迟迟没有相应。 但是…

华为---OSPF协议优先级、开销(cost)、定时器简介及示例配置

OSPF协议优先级、开销、定时器简介及示例配置 路由协议优先级&#xff1a;由于路由器上可能同时运行多种动态路由协议&#xff0c;就存在各个路由协议之间路由信息共享和选择的问题。系统为每一种路由协议设置了不同的默认优先级&#xff0c;当在不同协议中发现同一条路由时&am…

【Go 基础篇】Go语言结构体之间的转换与映射

在Go语言中&#xff0c;结构体是一种强大的数据类型&#xff0c;用于定义和组织不同类型的数据字段。当我们处理复杂的数据逻辑时&#xff0c;常常需要在不同的结构体之间进行转换和映射&#xff0c;以便实现数据的转移和处理。本文将深入探讨Go语言中结构体之间的转换和映射技…

【算法与数据结构】112、LeetCode路径总和

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题通过计算根节点到叶子节点路径上节点的值之和&#xff0c;然后再对比目标值。利用文章【算法和数据…

SpringBoot集成JWT token实现权限验证

JWTJSON Web Token 1. JWT的组成 JWTHeader,Payload,Signature>abc.def.xyz 地址&#xff1a;JSON Web Tokens - jwt.er 1.1 Header Header:标头。 两个组成部分&#xff1a;令牌的类型&#xff08;JWT&#xff09;和所使用的签名算法&#xff0c;经过Base64 Url编码后形成…

一、Mycat2介绍与下载安装

第一章 入门概述 1.1 是什么 Mycat 是数据库中间件。 1、数据库中间件 中间件&#xff1a;是一类连接软件组件和应用的计算机软件&#xff0c;以便于软件各部件之间的沟 通。 例子&#xff1a;Tomcat&#xff0c;web中间件。 数据库中间件&#xff1a;连接java应用程序和数据库…

CSS3D+动画

CSS3D 1.css3D 给父元素设置 perspective:景深:近大远小的效果900-1200px这个范围内 transform-style:是否设置3D环境 flat 2D环境 默认值 perserve-3D环境 3D功能函数 1.位移: translateZ()translate3D(x,y,z) <!DOCTYPE html> <html lang"en"><h…

Elasticsearch:自动使用服务器时间设置日期字段并更新时区

在大多数情况下&#xff0c;你的数据包含一个以 create_date 命名的字段。 即使没有日期字段&#xff0c;处理各种格式和时区的日期对数据仓库来说也是一个重大挑战。 与此类似&#xff0c;如果要检测变化的数据&#xff0c;则必须准确设置日期字段。 在 Elasticsearch 中还有…

windows vmware17虚拟机导出、导入

我采用的是vmware17版本的虚拟机软件 直接拷贝VM虚拟机文件 导出 查看虚拟机所在路径 复制整个文件夹&#xff0c;可以先压缩介绍文件大小&#xff0c;拷贝到需要还原该虚拟机的电脑上 导入 在目的电脑上需要安装vnware17版本的虚拟机软件 直接打开vmware17&#xff0c; 选…