【高并发内存池】第五弹---内存申请过程联调实战

✨个人主页:熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】【项目详解】

目录

1、申请内存过程联调

1.1、测试一

1.2、测试二


1、申请内存过程联调

此处进行调试,我们需要调用前面封装的申请内存函数ConcurrentAlloc() 。每个线程第一次调用该函数都会通过TLS获取到自己专属的thread cache对象然后每个线程就可以通过自己对应的thread cache申请对象了。

  • 注意:此处直接进行编译可能会出现问题,在C++的algorithm头文件中有一个min函数,这是一个函数模板,而在Windows.h头文件中也有一个min,这是一个宏。由于调用函数模板时需要进行参数类型推演,因此当我们调用min函数时,编译器会优先匹配Windows.h当中以宏的形式实现的min,此时当我们以std::min的形式调用min函数就会产生报错,此时我们只能选择将std::去掉,让编译器调用Windows.h当中的min

1.1、测试一

由于在多线程场景下调试观察起来非常麻烦,这里先不考虑多线程场景,看看在单线程场景下代码的执行逻辑是否符合我们的预期,其次,我们这里只简单观察在一个桶当中的内存申请即可。

下面该线程进行五次内存申请 ,这五次内存申请的字节数最终都对齐到了8,此时当线程申请内存时就只会访问到thread cache的第0号桶

复制代码
void TestConcurrentAlloc1()
{
	void* p1 = ConcurrentAlloc(6);
	void* p2 = ConcurrentAlloc(8);
	void* p3 = ConcurrentAlloc(1);
	void* p4 = ConcurrentAlloc(7);
	void* p5 = ConcurrentAlloc(8);

	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;
	cout << p4 << endl;
	cout << p5 << endl;
}

当线程第一次申请内存时 ,需要通过TLS获取到自己专属的thread cache对象然后通过thread cache对象进行内存申请

在申请内存时需要通过计算索引到了thread cache的第0号桶,但此时thread cache的第0号桶中是没有对象的,因此thread cache需要向central cache申请内存

1、在向central cache申请内存前,首先通过NumMoveSize函数计算出 ,thread cache一次最多向central cache申请8字节大小对象的个数是512 ,但由于我们采用的慢开始反馈调节算法,因此还需要将上限值与自由链表中的_maxSize值进行比较,而此时对应的自由链表_maxSize的值是1,因此确定本次thread cache向central cache申请8字节对象的个数是1个

2、在此之后会将该自由链表中_maxSize的值自增1,下一次thread cache再向central cache申请8字节对象个数就会是2,直到超出NumMoveSize函数计算出的对象个数。

在thread cache向central cache申请对象之前 ,需要先将central cache的0号桶的锁加上,然后再从该桶获取一个非空的span。

在central cache的第0号桶获取非空span时先遍历对应的span双链表,看看有没有非空的span,但此时肯定是没有的,因此在这个过程中我们无法找到一个非空的span。

那么此时central cache就需要向page cache申请内存了 ,但在此之前需要先把central cache第0号桶的锁解掉,然后再将page cache的大锁给加上,之后才能向page cache申请内存。

在向page cache申请内存时 ,由于central cache一次给thread cache8字节对象的上限是512,对应就需要4096字节,所需字节数不足一页就按一页算,所以这里central cache就需要向page cache申请一页的内存块

此时page cache的第1个桶以及之后的桶当中都是没有span的 ,因此page cache需要直接向堆申请一个128页的span

通过监视窗口可以看到 ,用于管理申请到的128页内存的span信息

验证一下,按页向堆申请的内存块的起始地址和页号之间是可以相互转换的

现在将申请到的128页的span插入到page cache的第128号桶当中 ,然后再调用一次NewSpan,在这次调用的时候,虽然在1号桶当中没有span,但是在往后找的过程中就一定会在第128号桶找到一个span

此时我们就可以把这个128页的span拿出来,切分成1页的span和127页的span将1页的span返回给central cache,而把127页的span挂到page cache的第127号桶即可。

从page cache返回后,就可以把page cache的大锁解掉了 ,但紧接着还要将获取到的1页的span进行切分,因此这里没有立刻重新加上central cache对应的桶锁。

在进行切分的时候先通过该span的起始页号得到该span的起始地址,然后通过该span的页数得到该span所管理内存块的总的字节数

在确定内存块的开始和结束后 ,就可以将其切分成一个个8字节大小的对象挂到该span的自由链表中了。在调试过程中通过内存监视窗口可以看到,切分出来的每个8字节大小的对象的前四个字节存储的都是下一个8字节对象的起始地址

当切分结束后再开启central cache第0号桶的桶锁 ,然后将这个切好的span插入到central cache的第0号桶中,最后再将这个非空的span返回,此时就获取到了一个非空的span

由于thread cache只向central cache申请了一个对象 ,因此拿到这个非空的span后,直接从这个span里面取出一个对象即可,此时该span的_useCount也由0变成了1

由于此时thread cache实际只向central cache申请到了一个对象 ,因此直接将这个对象返回给线程即可



当线程第二次申请内存块时就不会再创建thread cache了因为第一次申请时就已经创建好了,此时该线程直接获取到对应的thread cache进行内存块申请即可

该线程第二次申请8字节大小的对象时此时thread cache的0号桶中还是没有对象的因为第一次thread cache只向central cache申请了一个8字节对象,因此这次申请时还需要再向central cache申请对象

这时thread cache向central cache申请对象时 ,thread cache第0号桶中自由链表的_maxSize已经慢增长到2了,所以这次在向central cache申请对象时就会申请2个。如果下一次 thread cache再向central cache申请8字节大小的对象 ,那么central cache会一次性给 thread cache3个 ,这就是所谓的慢增长

但由于第一次central cache向page cache申请了一页的内存块,并将其切成了1024个8字节大小的对象,因此这次thread cache向central cache申请2个8字节的对象时,central cache的第0号桶当中是有对象的直接返回两个给thread cache即可,而不用再向page cache申请内存了。

线程实际申请的只是一个8字节对象 ,因此thread cache除了将一个对象返回之外,还需要将剩下的一个对象挂到thread cache的第0号桶当中



线程第三次申请1字节的内存时 ,由于1字节对齐后也是8字节,此时thread cache也就不需要再向central cache申请内存块了,直接将第0号桶当中之前剩下的一个8字节对象返回即可

1.2、测试二

为了进一步测试代码的正确性,我们可以做这样一个测试:让线程申请1024次8字节的对象,然后通过调试观察在第1025次申请时,central cache是否会再向page cache申请内存块

cpp 复制代码
void TestConcurrentAlloc2()
{
	for (size_t i = 0; i < 1024; ++i)
	{
		void* p1 = ConcurrentAlloc(6);
		cout << p1 << endl;
	}

	void* p2 = ConcurrentAlloc(8);
	cout << p2 << endl;
}

因为central cache第一次就是向page cache申请的一页内存,这一页内存被切成了1024个8字节大小的对象,当这1024个对象全部被申请之后,再申请8字节大小的对象时central cache当中就没有对象了此时就应该向page cache申请内存块

这次central cache在向page cache申请一页的内存时page cache就是将127页span切分成了1页的span和126页的span了,然后central cache拿到这1页的span后,又会将其切分成1024块8字节大小的内存块以供thread cache申请

相关推荐
算AI4 小时前
人工智能+牙科:临床应用中的几个问题
人工智能·算法
我不会编程5555 小时前
Python Cookbook-5.1 对字典排序
开发语言·数据结构·python
李少兄5 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
懒羊羊大王&5 小时前
模版进阶(沉淀中)
c++
无名之逆5 小时前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
似水এ᭄往昔5 小时前
【C语言】文件操作
c语言·开发语言
啊喜拔牙5 小时前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
owde6 小时前
顺序容器 -list双向链表
数据结构·c++·链表·list
xixixin_6 小时前
为什么 js 对象中引用本地图片需要写 require 或 import
开发语言·前端·javascript
GalaxyPokemon6 小时前
Muduo网络库实现 [九] - EventLoopThread模块
linux·服务器·c++