高并发内存池项目(3)——项目框架介绍与实现线程池

news2024/9/25 7:21:30

一,项目的整体架构

这个高并发内存池的主要分为三层,分别是TheradCache层,CentralCache层,PageCache层。如下图所示:

二,原理讲解

当我们来了一个任务要申请内存时,先经过第一层ThreadCache。ThreadCache层有如下特点:

1,对于每一个线程都有一个单独的ThreadCache对象。

2,最多分配256K的内存,如果超过了就向下一层CentralCache层申请内存。

3,内存分配的方法如下:

根据上图,我来做如下的解释:

1,ThreadCache得结构就像是一个哈希表后面挂着FreeList。

2,当我们申请内存时要先根据申请内存的大小决定该向那个下标下的那个自由链表申请内存。

3,当FreeList存在时先将FreeList内的空间利用起来,如果没有的话那就向CentralCache申请空间。 

三,ThreadCache层实现详解

1,ThreadCache类实现框架

class ThreadCache
 {
 public:
	 //申请空间
	 void* Allocate(size_t sz);
	 //释放空间
	 void Deallocate(void* ptr, size_t sz);
	 //向中心缓存申请空间
	 void* FetchFromCentralCache(size_t index, size_t size);
     //当线程缓存中的自由链表的长度过长时就要将线程缓存中的部分内存向下还给CentralCache
	 void ListTooLong(FreeList&list,int size);

 private:

	 //ThreadCache其实就是一个自由链表的数组
	 //链表的大小有多大呢?288,在后面的模块中可以计算
	 FreeList _threadCache[208];
 };

 //本地线程缓存,要去申请和释放这个本地线程缓存
 static _declspec(thread)ThreadCache* ptlsThread = nullptr;

2,具体细节

1,FreeList实现
//定义一个自由链表类
class FreeList
{
public:
	//在只有链表中头插一个对象
	void push(void* obj)
	{
		//插入的obj不能为nullptr
		assert(obj == nullptr);
		//取出下一个节点的地址
		void* NextObj = *(void**)_freeList;
		//头插
		*(void**)obj = NextObj;
		_freeList = obj;
	}

	//在自由链表中头删一个对象
	void* pop()
	{
		assert(_freeList);
		//头删
		void* obj = _freeList;
		void* NextObj = *(void**)_freeList;
		_freeList = NextObj;
		return obj;
	}

	bool Empty()
	{
		return _freeList == nullptr;
	}
private:
	void* _freeList;
};

FreeList的实现其实就和前面的定长内存池实现相同,详细的实现细节可以参考博客:定长池的实现 

 2,实现每一个线程绑定每一个ThreadCache对象
//本地线程缓存,要去申请和释放这个本地线程缓存
 static _declspec(thread)ThreadCache* ptlsThread = nullptr;

如上代码便可以实现每一个线程绑定一个ThreadCache。具体的介绍可以看如下文章:TLS介绍 

 3,分配内存Allocate函数介绍
void* ThreadCache::Allocate(size_t sz)
{
	//申请的数量不能超过MaxNum
	assert(sz > MaxNum);
	//先计算对齐数
	int AlignNum = AlianRule::AlignUp(sz);
	//计算哈希桶的链表的下标
	int index = Index(sz);
	//优先申请自由链表中的值
	if (!_threadCache[index].Empty())
	{
		return _threadCache[index].pop();
	}
	//自由链表为空就向中心缓存申请
	return FetchFromCentralCache(index, AlignNum);
}

 Allocate的实现具体思路如下:

1,确定我要申请的空间小于256*1024字节大小(MaxNum)。

2,计算对齐数:对齐数用于表示在FreeList内的空间没有时要向CentralCache申请的内存大小。

3,计算下标,这个index表示哈希桶下标,计算出下标后便可以先向自由链表申请内存。

4,若相应下标的自由链表为空就向下一层调用函数FetchFromCentralCache(index, AlignNum)申请内存。

计算对齐数的代码如下:

//对齐规则
class AlianRule
{
public:
	static inline size_t _AlignUp(int Sz,int AlignNum)
	{
		 //利用位移计算对齐数可以提高效率
		return (Sz+AlignNum-1)&~(AlignNum - 1);
		 
	};

	static size_t AlignUp(int Sz)
	{
		if (Sz<=128)
		{
			return _AlignUp(Sz, 8);
		}
		else if (Sz<=1024)
		{
			return _AlignUp(Sz, 16);
		}
		else if (Sz <= 8 * 1024)
		{
			return _AlignUp(Sz, 128);
		}
		else if (Sz <= 64 * 1024)
		{
			return _AlignUp(Sz, 1024);
		}
		else if (Sz <= 256 * 1024)
		{
			return _AlignUp(Sz, 8 * 1024);
		}
		else
		{
			assert(Sz >256 * 1024);
			return -1;
		}
	}
};

依据的规则是:

按照上面的规则来对齐,可以减少对齐时的情况并且也能减少内碎片率。 

计算下标函数代码如下:

static inline size_t _Index(size_t bytes, size_t align_shift)
{
	return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;
}

// 计算映射的哪一个自由链表桶
static inline size_t Index(size_t bytes)
{
	assert(bytes <= MaxNum);
	// 每个区间有多少个链
	static int group_array[4] = { 16, 56, 56, 56 };
	//计算在那个桶
	if (bytes <= 128)
		return _Index(bytes, 3);
	else if (bytes <= 1024)
		return _Index(bytes - 128, 4) + group_array[0];
	else if (bytes <= 8 * 1024)
		return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];
	else if (bytes <= 64 * 1024)
		return _Index(bytes - 8 * 1024, 10) + group_array[2] + group_array[1]
		+ group_array[0];
	else if (bytes <= 256 * 1024)
		return _Index(bytes - 64 * 1024, 13) + group_array[3] +
		group_array[2] + group_array[1] + group_array[0];
	else
		return -1;
}

 这里计算的下标都会对应到的哈希桶的下标。然后我们便可以操作对应下标的自由链表,完成响应的要求。这里使用位移的写法主要目的还是为了提高效率,减少加减法。

FetchFromCentralCache(index, AlignNum)函数

则个函数的实现在下一层,不过现在可以先了解下这个函数。这个函数的主要目的便是在对应下标的自由链表的内存不够时为ThreadCache向下一层申请缓存,两个参数分别指明了要为那个哈希桶下的只有链表申请内存以及要申请的内存的大小。

4,归还内存的Deallocate函数
void  ThreadCache:: Deallocate(void* ptr, size_t sz)
{
	assert(ptr);
	//算出线程在那个桶
	int index = Index(sz);
	//插入到自有链表中
	_threadCache[index].push(ptr);
}

相比于分配内存的Allocate函数,Deallocate函数的实现就更加简单了。主要步骤就是计算出哈希桶的下标。然后让哈希桶对应下标的自由链表进行插入操作。

5, FetchFromCentralCache函数实现

该函数的功能是在线程缓存不够内除时向下一层的内存申请缓存。实现代码如下

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

	 //开始向CentralCache获取span内存
	 void* start = nullptr;
	 void* end = nullptr;
	 //从CentralCache获取内存,放到start和end中
	 int actualNum = CentralCache::GetInstance()->FetchRangeObj(start,end,batchNum,size);
	 assert(actualNum > 0);
	 //头插插入到_freelist[index]中
	 if (actualNum == 1)
	 {
		assert(end == start);
		 return start;
	 }
	 else
	 {    //我申请了一串内存,我只给一个给申请者使用其它的就链入到_freelist中,增加的个数就是actualNum-1
		 _freelist[index].pushRange(NextObj(start), end,actualNum-1);
		 return start;
	 }
 }

在这个函数中还调用了另外的两个函数,分别是Central内部的 CentralCache::GetInstance()->FetchRangeObj(start,end,batchNum,size)以及在自由链表内部实现的函数pushRange函数。这两个函数的实现方法如下。


size_t CentralCache:: FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	//1,先获取大的span
	int index = Index(size);
	CentralCache::_List[index]._mux.lock();
	Span* span = GetOneSpan(&_List[index], size);
	//断言下获取到的span和size
	assert(span);
	assert(size <= MaxBytes);

	//开始获取:end,start从span的自由链表处开始出发
	start = span->_freelist;
	end = span->_freelist;
	int i = 0;
	int actualNum = 1;
	while (i++ < batchNum-1 && NextObj(end) != nullptr)
	{
		end = NextObj(end);
		actualNum++;
	}

	//截断
	span->_freelist = NextObj(end);
	NextObj(end) = nullptr;
	span->_useCont += actualNum;
	CentralCache::GetInstance()->_List[index]._mux.unlock();
	//返回相应的块数
	return actualNum;
}

 自由链表内部实现的插入函数,使用的插入方式是头插。

void pushRange(void* start, void* end,int n)
	{
           
       //使用头插的方式插入到只有链表中
		NextObj(end) = _freeList;
		_freeList = start;
       //将自由链表中的个数加上n
		_size += n;
	}
 6,ListTooLong函数实现

该函数的功能是在线程缓存这一层的自由链表过长时通过调用centralcache函数将内存归还给中心缓存。

代码实现如下:

void ThreadCache:: ListTooLong(FreeList& list, int size)
 {
	 //size一定比MaxSize()大
	 assert(list.Size()>=list.MaxSize());
	 void* start = nullptr;
	 void* end = nullptr;

	 //取下一段内存
	 void*ptr = list.popRange(start,end,list.MaxSize());
	 //放到对应的Span中
	 CentralCache::GetInstance()->ReleaseListToSpans(ptr,size);
 }

实现以上的功能后便实现了线程缓存,接下来便可以向下继续实现中心缓存。

 

 

 

 

 

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

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

相关文章

基于AI+多技术融合在流域生态系统服务评价、水文水生态分析、碳收支、气候变化影响、制图等领域中的应用

流域生态系统服务在环境保护与资源管理中具有不可替代的重要性。随着全球气候变化和人类活动对自然环境的压力日益增大&#xff0c;流域生态系统的稳定性和健康状况面临严峻挑战。水资源短缺、洪水频发、水质污染、生物多样性减少等问题&#xff0c;正在威胁流域内及其下游区域…

STMCuBeMX新建项目的两种匪夷所思的问题

错误一、保存地址名中有中文 错误&#xff1a;error1-haveCHinese_有中文\error1-haveCHinese_有中文.axf: error: L6002U: Could not open file error1-havechinese_???\stm32f1xx_it.o: No such file or directory 解决方法&#xff1a;重新导出&#xff0c;并且不要用中文…

Bandicam录制视频发白(过曝)如何解决?

Bandicam录制视频发白&#xff08;过曝&#xff09;如何解决&#xff1f; 一&#xff0c;问题现象二&#xff0c;解决方法 一&#xff0c;问题现象 录制视频时&#xff0c;视频播放的颜色比笔记本电脑上的颜色差别比较大&#xff0c;显示比实际的颜色发白。 二&#xff0c;解…

会计确认数据资产相关问题解读:权属和合规

会计确认数据资产相关问题中的权属和合规性是企业必须深入理解和重视的两个方面。 企业会计准则上明确了资产&#xff1a;企业合法拥有和控制的&#xff0c;预期会给企业带来经济利益的资源都叫资产。如何理解数据资产的权属及合规&#xff1f;如何确保企业合法拥有、控制数据…

Java 入门指南:Java 并发编程 —— 同步工具类 CyclicBarrier(循环屏障)

文章目录 同步工具类CyclicBarrier构造函数常用方法工作机制使用步骤适用场景CyclicBarrier与CountDownLatch的区别示例代码 同步工具类 JUC&#xff08;Java.util.concurrent&#xff09;是 Java 提供的用于并发编程的工具类库&#xff0c;其中包含了一些通信工具类&#xff…

泰勒斯威夫特是认真的:我已经做过研究我做出了选择,支持哈里斯 !呼吁粉丝赶紧投票!

泰勒说她将投票给哈里斯,因为"她为权利而战,我相信需要一个战士来捍卫他们。" 泰勒:这真的让我对人工智能产生了恐惧 当拥有3亿多粉丝的碧昂丝将主题曲授权给哈里斯的时候就已经暗示了她的倾向性 如果碧昂丝和斯威夫特6亿粉丝的哈里斯选票转化率为30%&#xff0c;…

全国各地身份证号开头6位数字及地区对照表

具体请前往&#xff1a;全国各地身份证号开头6位数字-省市县/区对照表

计算语言学(一)基础

概率论的几个概念 熵、互信息 神经网络基础 MLP CNN RNN Seq2Seq LSTM Transformer 语料库与知识库

L2线性回归模型

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 鸢尾花数据集的单变量与多变量预测 在这周学习如何使用 机器学习 模型对鸢尾花&#xff08;Iris&#xff09;数据集进行单变量与多变量预测。我们将使用鸢尾花…

北京中实新材料公司:安全筑基,共绘新材料产业新篇章

北京中实新材料有限责任公司(以下简称“北京中实”),作为中关村科技发展(控股)股份有限公司旗下的重要成员,近年来在安全生产、技术创新及企业合作等方面取得了显著进展。近期,公司围绕安全生产月及新材料研发中心成立等核心活动,展开了一系列富有成效的工作,进一步推动了企业的…

【Java算法】递归

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【算法工作坊】算法实战揭秘 &#x1f347;一.递归 概念 递归是一种解决问题的方法&#xff0c;其中函数通过调用自身来求解问题。这种方法的关键在于识别问题是否可以被分解为若干个相似但规模更小…

深入Redis:复杂的集群

广义的集群&#xff0c;可能说只要是多台机器组成了分布式系统&#xff0c;就可以称之为集群。 狭义的集群&#xff0c;指的是Redis提供的集群模式&#xff0c;这个集群模式之下&#xff0c;主要是解决存储空间不足的问题&#xff0c;以及如何拓展存储空间。 之前的哨兵模式&…

C++中string的简单实现

string的简单实现中一些函数的实现可以复用一些其他的函数来实现&#xff1b;比较重要的是在实现是深浅拷贝问题 目录 string的结构 实现构造和析构 reserve扩容 容量 push_back和append insert和erase的实现 swap的实现&#xff08;不是成员函数但是string类的友元&…

【c++】类和对象详解

✅博客主页:爆打维c-CSDN博客​​​​​​ &#x1f43e; &#x1f539;分享c语言知识及代码 来都来了! 点个赞给博主个支持再走吧~&#xff01; 一.类的定义 &#xff08;1&#xff09;类定义格式 class为类定义的关键字&#xff0c;定义一个类格式如下: class 类名{//代码…

kubelet 探针

目录 1 k8s中kubelet 探针的介绍 1.1 探针是由 kubelet 对容器执行的定期诊断: 1.2 Kubelet 可以选择是否执行在容器上运行的三种探针执行和做出反应&#xff1a; 1.3 ReadinessProbe 与 LivenessProbe 的区别 1.4 StartupProbe 与 ReadinessProbe、LivenessProbe 的区别 2 实…

CCRC-DSA数据安全评估师:网络安全风险评估

1.网络安全风险评估概述 1.1概念 在当今信息化时代&#xff0c;网络安全成为了组织不可或缺的一部分。 风险评估作为一种科学方法&#xff0c;其目的是对网络系统的保密性、完整性、可控性和可用性这四个核心安全属性进行深入分析。 这一过程不仅包括识别网络系统中存在的脆…

Snipaste无法使用F1、F3等快捷键的保姆级解决方法

在Snipaste中按F1、F3等快捷键无效的可能原因&#xff1a; 1. 软件设置&#xff1a; 检查Snipaste的设置&#xff0c;确保F1被正确设置为截屏热键&#xff0c;并确认没有其他软件占用或冲突。 2. 热键冲突&#xff1a; 笔记本电脑的功能键&#xff08;F1-F12&#xff09;通常…

MySQL 数据库:原理、应用与发展

摘要&#xff1a;本文深入探讨了 MySQL 数据库相关内容。首先介绍了 MySQL 作为开源关系型数据库管理系统的显著特点&#xff0c;包括易用性、跨平台性、高性能、可扩展性、开源免费以及数据安全性等方面。接着详细阐述了其安装与配置过程&#xff0c;涵盖在不同操作系统上的安…

STM32使用 :串口的接收与发送

一、串口 在 STM32 中&#xff0c;串口&#xff08;UART&#xff0c;通用异步收发传输器&#xff09;是用于串行通信的外设。它在嵌入式系统中的作用非常广泛&#xff0c;主要包括几个方面 数据通信 串口用于微控制器与其他设备之间的数据传输。这些设备可以是其他微控制器、…

F12抓包08:查看网站Cookie

课程大纲 1、查看Cookie 1. 应用界面查看&#xff1a;按F12进入浏览器的开发者模式 - “应用”&#xff08;Application&#xff09; - Cookie&#xff0c;可查看Cookie并进行增、删、改、查操作。 2. 控制台命令行查看&#xff1a;按F12进入浏览器的开发者模式 - “控制台”&…