【C++项目】高并发内存池第四讲 申请内存过程介绍流程介绍

news2024/11/13 16:16:31

请添加图片描述

请添加图片描述

申请内存过程介绍

  • 1.主函数执行
  • 2.ThreadCache
  • 3.CentralCache
  • 4.PageCache

1.主函数执行

先从内存池申请内存
0
0002
获取ThreadCache对象,然后去ThreadCache对象的Allocate申请!

2.ThreadCache

在这里插入图片描述
这里要计算对齐函数和相应的桶的下标,这个之前介绍ThreadCache的框架设计的时候有说明,这里不多赘述,计算完对齐数和桶下标之后,如果当前的桶下面有内存对象就直接返回给上级,如果没有,就去中中心调度器CentralCache中申请

  • 申请函数 :FetchFromCentralCache(index, alignSize);。
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 >0);
	if (actualNum == 1)
	{
		assert(start == end);
		return start;
	}
	else
	{
		_freeLists[index].PushRange(NextObj(start), end, actualNum - 1);
		return start;
	}
}

这里采用满增长双重机制来规范分配规则(之前也有介绍过),计算出在该机制规则下,ThreadCache想向CentralCache中取多大的内存,batchNum,然后去CentralCache申请,但是CentralCache不一定会给你这么多的内存(有可能对应的桶下面没能满足 但是最少有一个单位的内存 不然CentralCache就会继续向上一级申请内存了) 基于这种策略,所以有一个实际分配回来的内存actualNum,如果返回的内存单位只有一个,就直接返回给上级调用对象,如果有多余的内存就插入对应的空闲列表中等待下次对象申请直接 分配

//头插一段范围的节点
	void PushRange(void* start, void* end, size_t n)
	{
		assert(start && end);
		NextObj(end) = _freeList;
		_freeList = start;
		_size += n;
	}

3.CentralCache

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;

	_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));
	PageCache::GetInstance()->_pageMtx.unlock();//到这一步程序就已经申请到一个span了

	//对span进行切分 此过程不需要加锁 因为其他的线程访问不到这个span
	//(这时的span没切分好也没挂到相应位置)

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

	char* start = (char*)(span->_page_id << PAGE_SHIFT);
	size_t bytes = span->_n *8*1024;
	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;
}
  1. FetchRangeObj 函数用于从 CentralCache 中获取一定数量的对象,并且返回这些对象的实际数量。它的参数包括 star(起始对象指针)、end(结束对象指针)、batchNum(要获取的对象数量),以及 size(对象大小)。

  2. 首先,根据对象大小 size 计算出对象应该存储在哪个 CentralCache 桶中,这通过 SizeClass::Index 函数实现。

  3. 然后,获取对应桶的互斥锁,因为这里涉及到多线程并发访问。

  4. 通过 GetOneSpance 函数从当前桶中获取一个 Span 对象。如果找到一个非空的 Span,则继续下一步;否则,会释放当前桶的互斥锁,并尝试从 PageCache 获取一个新的 Span

  5. 获取到的 Span 可能包含多个对象,所以接下来需要从 Span 中获取 batchNum 个对象。这里维护了 starend 两个指针来跟踪获取的对象的范围。一个 while 循环逐个获取对象并将 starend 指针移动到下一个对象。

  6. 最后,释放当前桶的互斥锁,返回实际获取的对象数量。

  7. GetOneSpance 函数用于从当前桶中获取一个非空的 Span 对象。如果当前桶中没有非空的 Span,它会释放当前桶的互斥锁,然后尝试从 PageCache 获取一个新的 Span

  8. 如果找到一个非空的 Span,接下来就需要将 Span 中的大块内存切分为小对象并链接成自由链表。这部分代码主要用来处理大块内存到小对象的切分,确保内存连续性,并将小对象挂到自由链表中。

  9. 最后,将切好的 Span 加入当前桶的链表,并返回该 Span

总之,这段代码的目的是从 CentralCache 中获取一定数量的对象,如果当前桶没有足够的对象,则会先从 PageCache 获取一个 Span,然后将该 Span 切分成小对象,并挂到自由链表中。最后,返回获取的对象数量。这是内存分配的关键逻辑,确保了对象的高效分配

4.PageCache

//获取一个K页的span
 Span* PageCache::Newspan(size_t k)
{
	assert(k > 0);
	//大于128页的就直接向堆中申请
	if (k > NPAGES - 1)
	{
		//
		void* ptr = SystemAlloc(k);
		Span* span = pageCache->_spanPool.New();
		span->_page_id = (PAGE_ID)ptr >> PAGE_SHIFT;
		span->_n = k;
		pageCache->_idSpanMap.set(span->_page_id, span);
		return span;
	}

	//先检查第K个桶里面有没有span
	if (! pageCache->_spanList[k].Empty())
	{
		/*Span* kSpan = pageCache->_spanList[k].PopFront();
		for (PAGE_ID i = 0; i < kSpan->_n; i++)
		{
			pageCache->_idSpanMap.set(kSpan->_page_id + i, kSpan);
		}
		return kSpan;*/
		return pageCache->_spanList->PopFront();
	}

	//第K个桶是空的 检查一下后面的桶里面有没有span
	for (size_t i = k + 1; i < NPAGES; ++i)
	{
		if (!pageCache->_spanList[i].Empty())
		{
			Span* nspan = pageCache->_spanList[i].PopFront();
			Span* kspan = new Span;
			//Span* kspan= pageCache->_spanPool.New();

			//在nspan头部切一个k页下来
			//k页的span返给centralcache剩下的挂在对应的映射位置上
			kspan->_page_id = nspan->_page_id;
			kspan->_n = k;

			nspan->_page_id += k;
			nspan->_n -= k;
			pageCache->_spanList[nspan->_n].PushFront(nspan);
			/*
			//存储nSpan的首位页号跟nspan映射 
			//存储nSapn的首位页号跟nspan映射
			//方便page cache回收内存时,进行合并查找
			//_idSpanMap[nSpan->_pageId] = nSpan;
			pageCache->_idSpanMap.set(nspan->_page_id, nspan);
			//_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;
			pageCache->_idSpanMap.set(nspan->_page_id + nspan->_n - 1, nspan);
			//建立id和span的映射,方便central cache回收小块内存时,查找对应的span
			for (PAGE_ID i = 0; i < kspan->_n; ++i)
			{
				//_idSpanMap[kSpan->_pageId + i] = kSpan;
				pageCache->_idSpanMap.set(kspan->_page_id + i, kspan);
			}*/

			return kspan;
		}
	}
	//走到这个位置,后面没有大页的span
	//这时候就去找堆要一个128的span
	Span* bigSpan = new Span;
	//Span* bigSpan = pageCache->_spanPool.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->_page_id = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigSpan->_n = NPAGES - 1;

	pageCache->_spanList[bigSpan->_n].PushFront(bigSpan);

	return Newspan(k);
} 
 Span* PageCache::MapObjectToSpan(void* obj)
 {
	 PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);

	 //std::unique_lock<std::mutex> lock(_pageMtx);//出了作用域自动释放锁
	 //auto ret = _idSpanMap.find(id);
	 //if (ret != _idSpanMap.end())
	 //{
	 //	return ret->second;
	 //}
	 //else
	 //{
	 //	assert(false);
	 //	return nullptr;
	 //}
	 auto ret = (Span*)pageCache->_idSpanMap.get(id);
	 assert(ret != nullptr);
	 return ret;
 }

一样的道理,仔细分析代码不难推断其逻辑,核心功能就是:当CentralCache向它申请内存时,它要是有空闲的内存就分配给CentralCache,要是没有,就想系统申请,然后会多申请一些,挂到相应的桶(挂桶规则之前介绍框架设计的时候介绍过)方便下一次分配。
点赞支持一下,持续更新~请添加图片描述

请添加图片描述

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

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

相关文章

JavaScript事件处理:探索DOM事件和事件监听器

目录 DOM事件简介 什么是事件&#xff1f; 事件处理程序 在HTML中添加事件处理程序 事件类型 事件对象 事件监听器 事件冒泡与事件捕获 常见的事件类型和用法 总结 在Web开发中&#xff0c;JavaScript事件处理是一个重要的概念。通过事件处理&#xff0c;我们可以对用…

【linux】倒计时小程序

倒计时小程序 第一步&#xff08;创建一个目录&#xff09;mkdir processbar&#xff1a; 进入目录cd processbar&#xff1a; 第二步&#xff08;创建一个.h文件【声明】&#xff0c;两个.c文件【实现】和【函数调用】的文件&#xff09;touch processBar.h touch processB…

谷歌计划从Chrome119起测试IP隐私保护功能

目前&#xff0c;谷歌正为Chrome浏览器测试一项新的“IP保护”功能。因为该公司认为用户IP地址一旦被黑客滥用或秘密跟踪&#xff0c;都可能导致用户隐私信息泄露。 而这项功能可通过代理服务器屏蔽用户的IP地址&#xff0c;以增强用户的隐私性&#xff0c;这样就可以尽量在确…

centos7安装mysql8.0

mysql官网地址&#xff1a;MySQL :: Download MySQL Community Server (Archived Versions) 1.上传到 /usr/local 路径下 2.解压 tar -xvf mysql-8.0.31-linux-glibc2.12-x86_64.tar.xz3.重命名 mv mysql-8.0.31-linux-glibc2.12-x86_64.tar.xz mysql 4.创建mysql用户组和用…

甘特图组件DHTMLX Gantt用例 - 如何自定义任务、月标记和网格新外观

dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的所有需求&#xff0c;是最完善的甘特图图表库。 本文将为大家揭示DHTMLX Gantt自定义的典型用例&#xff0c;包括自定义任务、网格的新外观等&#xff0c;来展示其功能的强大性&…

华为eNSP配置专题-策略路由的配置

文章目录 华为eNSP配置专题-策略路由的配置0、概要介绍1、前置环境1.1、宿主机1.2、eNSP模拟器 2、基本环境搭建2.1、终端构成和连接2.2、终端的基本配置 3、配置接入交换机上的VLAN4、配置核心交换机为网关和DHCP服务器5、配置核心交换机和出口路由器互通6、配置PC和出口路由器…

【详细】Java网络通信 TCP、UDP、InetAddress

一、网络程序设计基础 1.局域网与因特网 为了实现两台计算机的通信&#xff0c;必须用一个网络线路连接两台计算机&#xff08;服务器<-->网络<-->客户机&#xff09;。 服务器是指提供信息的计算机或程序&#xff0c;客户机是指请求信息的计算机或程序。网络用…

HTML,CSS实现鼠标划过头像,头像突出变大(附源码)

话不多说&#xff0c;先上代码 先看原图&#xff1a; 再看 鼠标放上去后的图&#xff1a; 是不是明显感觉到 人物头像突出了一些&#xff0c;而且还增加了阴影部分的效果呢&#xff1f; 直接上代码&#xff01;&#xff01;&#xff01; <!--由于我的 img 标签放的是循环后…

一文总结 MetaQ/RocketMQ 原理

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

c语言基础:L1-058 6翻了

“666”是一种网络用语&#xff0c;大概是表示某人很厉害、我们很佩服的意思。最近又衍生出另一个数字“9”&#xff0c;意思是“6翻了”&#xff0c;实在太厉害的意思。如果你以为这就是厉害的最高境界&#xff0c;那就错啦 —— 目前的最高境界是数字“27”&#xff0c;因为这…

VDA到Excel方案介绍之自定义邮件接收主题

VDA标准是德国汽车工业协会&#xff08;Verband der Automobilindustrie&#xff0c;简称VDA&#xff09;制定的一系列汽车行业标准。这些标准包括了汽车生产、质量管理、供应链管理、环境保护、安全性能等方面的规范和指南。VDA标准通常被德国和国际上的汽车制造商采用&#x…

会声会影Corel VideoStudio2024旗舰版新功能介绍及会声会影2024这款软件怎么样?

会声会影Corel VideoStudio2024旗舰版一款功能丰富的视频编辑软件。会声会影2023简单易用&#xff0c;具有史无前例的强大功能&#xff0c;拖放式标题、转场、覆叠和滤镜&#xff0c;色彩分级、动态分屏视频和新增强的遮罩创建器&#xff0c;超越基本编辑&#xff0c;实现影院级…

使用Scala和Sttp库编写爬虫程序

以下是一个使用Scala和Sttp库编写的视频爬虫程序&#xff0c;该程序使用了proxy来获取IP。请注意&#xff0c;这个示例需要在上找到一个具体的视频链接&#xff0c;然后将其传递给crawlVideo函数。 import scala.util.{Failure, Success} import scala.concurrent.{Future, Ex…

Linux驱动调试方法(高级字符设备八)

在编写Linux驱动程序时&#xff0c;通常都使用 printk 函数打印相应的提示信息从而对驱动进行调试&#xff0c;除了printk 函数之外&#xff0c;还有其他的方式来调试驱动呢。 一、dump_stack 函数 作用:打印内核调用堆栈&#xff0c;并打印函数的调用关系。 这里以最简单的 h…

传奇黑客斯诺登,现状如何了?

曾经曝光米国棱镜计划的英雄斯诺登&#xff0c;现在怎么样了&#xff1f; 要说老米最恨的人有哪些&#xff0c;那斯诺登肯定榜上有名。斯诺登曾经是一名军人&#xff0c;退伍后在中情局负责维持网络安全&#xff0c;在得知老米的棱镜计划之后&#xff0c;出于人道主义&#xff…

面试题之JavaScript经典for循环(var let)

如果你也在面试找工作&#xff0c;那么也一定遇到过这道for循环打印结果的题&#xff0c;下面我们来探讨下 var循环 for(var i 0; i < 10; i) {setTimeout(function(){console.log(i)}); } 先把答案写出来 下面来讲一下原因&#xff1a; 划重点 ① var ②setTimeout() …

发卡系统微信小程序源码/云盘发卡系统源码带PC端/自动发卡小程序源码(开源)

源码介绍&#xff1a; 最新开源的发卡系统微信小程序源码&#xff0c;这是一款云盘发卡系统源码&#xff0c;还带了电脑PC端。它是一款实用方便操作自动发卡小程序源码&#xff0c;它使用ERMEB云盘发卡&#xff0c;能为用户提供便捷的发卡服务。 源码框架&#xff1a; 系统采…

Unity的碰撞检测(四)

温馨提示&#xff1a;本文基于前一篇“Unity的碰撞检测(三)”继续探讨两个游戏对象具备刚体的触发检测&#xff0c;阅读本文则默认已阅读前文。 &#xff08;一&#xff09;测试说明 在基于两个游戏对象都具备触发器和刚体且属性一致的条件下&#xff0c;若二者刚体的BodyType…

CTF-php特性绕过

注意&#xff1a;null0 正确 nullflase 错误 Extract变量覆盖 <?php$flagxxx; extract($_GET);if(isset($shiyan)){ $contenttrim(file_get_contents($flag));//trim移除引号if($shiyan$content){ echoctf{xxx}; }else{ echoOh.no;} }?> extract() 函数从数组中将…

java中Map常见的面试问题,扩容问题,转红黑树的前提,解决Hash哈希冲突的方法

Map集合常见面试题 如何解决 解决哈希碰撞的方法 1链地址法(hashMap的处理方式) 把hash表的每个单元作为链表的头节点。当发生冲突时放入到同一个hash值计算索引对应的链表。 2开放定址法 发生冲突后寻找下一个地址 3再次hash法 对hash值再次进行hash计算 4建立公共溢出区…