2024年最全项目:高并发内存池_高并发内存池项目作为简历项目,2024年最新分享两道阿里P7究极难度算法题

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

{
	assert(obj);

	// 头插
	//\*(void\*\*)obj = \_freeList; //\*(void\*\*)obj取obj头上4个或8个字节指向\_freeList
	NextObj(obj) = _freeList;
	_freeList = obj;

	++_size;
}

void PushRange(void\* start, void\* end, size_t n)
{
	NextObj(end) = _freeList;
	_freeList = start;

	// 测试验证+条件断点
	/\*int i = 0;
	void\* cur = start;
	while (cur)
	{
		cur = NextObj(cur);
		++i;
	}

	if (n != i)
	{
		int x = 0;
	}\*/

	_size += n;
}

void PopRange(void\*& start, void\*& end, size_t n)
{
	assert(n >= _size);
	start = _freeList;
	end = start;

	for (size_t i = 0; i < n - 1; ++i)
	{
		end = NextObj(end);
	}

	_freeList = NextObj(end);
	NextObj(end) = nullptr;
	_size -= n;
}

void\* Pop()
{
	assert(_freeList);

	// 头删
	void\* obj = _freeList;
	_freeList = NextObj(obj);
	--_size;

	return obj;
}

bool Empty()
{
	return _freeList == nullptr;
}

size_t& MaxSize()
{
	return _maxSize;
}

size_t Size()
{
	return _size;
}

private:
void* _freeList = nullptr;
size_t _maxSize = 1;
size_t _size = 0;
};


#### 自由链表的哈希桶跟对象大小的映射关系


**1、内存对其**  
 在此项目中,申请的内存小于256KB的,都会走三层缓存。那是不是我们要建立256KB个哈希桶呢?1-256KB都有对应的自由链表呢?  
 显然不是,那样的话太消耗资源。


本文采用内存对齐的方法,来建立哈希桶。


* 申请字节数在[1,128]Byte,采用8字节对其。就是你申请1到8字节空间,就给你8字节的内存。申请9到16字节的空间,就给你16字节的内存。
* 申请字节数在[1,1024]Byte,采用16字节对齐规则。
* 申请字节数在[1K,8K]Byte,采用128字节对齐规则。
* 申请字节数在[8K,64K]Byte,采用1K字节对齐规则。
* 申请字节数在[64K,256K]Byte,采用8K字节对齐规则。


将内存碎片控制在11%左右。  
 例:  
 假设需要129字节内存,对其之后就是144字节。系统会分配144字节空间。那么就浪费了15个字节的空间。15/144=10.4%


**2、怎么实现内存对其呢?**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/4280dc734708476bad75293c1230a09a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5LiA77yB,size_20,color_FFFFFF,t_70,g_se,x_16)  
 **3、怎么实现对其齐后的数据向自由链表申请内存呢?**  
 知道了对齐后的数据,怎么找到对应的自由链表呢  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/340045fcc65f496bbc0e6a42ca3e5dca.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5LiA77yB,size_20,color_FFFFFF,t_70,g_se,x_16)



// 计算对象大小的对齐映射规则
class SizeClass
{
public:
// 整体控制在最多10%左右的内碎片浪费
// [1,128] 8byte对齐 freelist[0,16)

//假设需要129字节,会分配给你144字节给你,就有15字节的浪费 15/144=0.104
// [128+1,1024] 16byte对齐 freelist[16,72)

//假设需要1025个字节,会分配给你1152字节给你,就有127字节的浪费 127/1152=0.11
// [1024+1,8\*1024] 128byte对齐 freelist[72,128)

// [8\*1024+1,64\*1024] 1024byte对齐 freelist[128,184)
// [64\*1024+1,256\*1024] 8\*1024byte对齐 freelist[184,208)

/\*size\_t \_RoundUp(size\_t size, size\_t alignNum)

{
size_t alignSize;
if (size % alignNum != 0)
{
alignSize = (size / alignNum + 1)*alignNum;
}
else
{
alignSize = size;
}

return alignSize;
}*/
// 1-8
static inline size_t _RoundUp(size_t bytes, size_t alignNum)
{
return ((bytes + alignNum - 1) & ~(alignNum - 1));
}

static inline size_t RoundUp(size_t size)
{
	if (size <= 128)
	{
		return \_RoundUp(size, 8);
	}
	else if (size <= 1024)
	{
		return \_RoundUp(size, 16);
	}
	else if (size <= 8\*1024)
	{
		return \_RoundUp(size, 128);
	}
	else if (size <= 64\*1024)
	{
		return \_RoundUp(size, 1024);
	}
	else if (size <= 256 \* 1024)
	{
		return \_RoundUp(size, 8\*1024);
	}
	else  //>256KB
	{
		return \_RoundUp(size, 1<<PAGE_SHIFT);
	}
}
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 <= MAX_BYTES);

	// 每个区间有多少个链
	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{
		assert(false);
	}

	return -1;
}

### 高并发内存池–central cache


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


![在这里插入图片描述](https://img-blog.csdnimg.cn/8e91fba4363e44f1a668e4476693d78d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5LiA77yB,size_20,color_FFFFFF,t_70,g_se,x_16)  
 **申请内存:**


1. 当thread cache中没有内存时,就会批量向central cache申请一些内存对象,这里的批量获取对象的数量使用了类似网络tcp协议拥塞控制的慢开始算法;central cache也有一个哈希映射的spanlist,spanlist中挂着span,从span中取出对象给thread cache,这个过程是需要加锁的,不过这里使用的是一个桶锁,尽可能提高效率。
2. central cache映射的spanlist中所有span的都没有内存以后,则需要向page cache申请一个新的span对象,拿到span以后将span管理的内存按大小切好作为自由链表链接到一起。然后从span中取对象给thread cache。
3. central cache的中挂的span中use\_count记录分配了多少个对象出去,分配一个对象给threadcache,就++use\_count


**释放内存:**


当thread\_cache过长或者线程销毁,则会将内存释放回central cache中的,释放回来时–use\_count。当use\_count减到0时则表示所有对象都回到了span,则将span释放回page cache,page cache中会对前后相邻的空闲页进行合并。


![在这里插入图片描述](https://img-blog.csdnimg.cn/ec22bee7a1a449399fb2fb127dea22ab.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5LiA77yB,size_20,color_FFFFFF,t_70,g_se,x_16)


**CentralCache 代码框架:**



#pragma once

#include “Common.h”

// 单例模式-饿汉(相当于一个全局静态变量)
class CentralCache
{
public:
static CentralCache* GetInstance() //获取一个实例对象,全局只有一个
{
return &_sInst;
}

// 获取一个非空的span
Span\* GetOneSpan(SpanList& list, size_t byte_size);

// 从中心缓存获取一定数量的对象给thread cache
size_t FetchRangeObj(void\*& start, void\*& end, size_t batchNum, size_t size);

// 将一定数量的对象释放到span跨度
void ReleaseListToSpans(void\* start, size_t byte_size);

private:
SpanList _spanLists[NFREELIST];

private:
CentralCache() //将构造函数私有,就不能随便创建对象
{}

CentralCache(const CentralCache&) = delete;  //拷贝构造也封死

static CentralCache _sInst;

};


**Span结构和Span的自由链表**



// 管理多个连续页大块内存跨度结构
struct Span
{
PAGE_ID _pageId = 0; // 大块内存起始页的页号
size_t _n = 0; // 页的数量

Span\* _next = nullptr;	// 双向链表的结构
Span\* _prev = nullptr;

size_t _objSize = 0;  // 切好的小对象的大小
size_t _useCount = 0; // 切好小块内存,被分配给thread cache的计数
void\* _freeList = nullptr;  // 切好的小块内存的自由链表

bool _isUse = false;          // 是否在被使用

};

// 带头双向循环链表
class SpanList
{
public:
SpanList()
{
_head = new Span;
_head->_next = _head;
_head->_prev = _head;
}

Span\* Begin()
{
	return _head->_next;
}

Span\* End()
{
	return _head;
}

bool Empty()
{
	return _head->_next == _head;
}

void PushFront(Span\* span)
{
	Insert(Begin(), span);
}

Span\* PopFront()
{
	Span\* front = _head->_next;
	Erase(front);
	return front;
}

void Insert(Span\* pos, Span\* newSpan)
{
	assert(pos);
	assert(newSpan);

	Span\* prev = pos->_prev;
	// prev newspan pos
	prev->_next = newSpan;
	newSpan->_prev = prev;
	newSpan->_next = pos;
	pos->_prev = newSpan;
}

void Erase(Span\* pos)
{
	assert(pos);
	assert(pos != _head);  //不能把头删了

	// 1、条件断点
	// 2、查看栈帧
	/\*if (pos == \_head)

{
int x = 0;
}*/

	Span\* prev = pos->_prev;
	Span\* next = pos->_next;

	prev->_next = next;
	next->_prev = prev;
}

private:
Span* _head;
public:
std::mutex _mtx; // 桶锁,不同的线程访问同一个桶才会有竞争
};


#### central cache的工作过程


1、映射到对应的桶中,去查看对应的Spanlist上的span是否有空间。


2、去遍历Spanlist上不为空的span,如果span中的内存不够一个批量的,那么有多少小块内存,就给多少,如果够一个批量的内存,就切除一个批量的空间给thread cache


3、如果Spanlist上的所有span都没有空间,则central cache需要向下一层pagecache 申请空间。申请到之后,对申请的空间进行切分,分成和桶对应大小的字节,组成一个span,挂在对应Spanlist上。建议切分的时候,采用尾插的方式,这样组成的span,内存地址连续,分配出去之后,可以提高CPU缓存利用率。


![在这里插入图片描述](https://img-blog.csdnimg.cn/cae4f5dac6c843a99f79287d78e016f6.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5LiA77yB,size_20,color_FFFFFF,t_70,g_se,x_16)  
 **central cache释放内存给thread cache,以及从thread cache回收内存框架**



#include “CentralCache.h”
#include “PageCache.h”

CentralCache CentralCache::_sInst;

// 获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
// 查看当前的spanlist中是否有还有未分配对象的span
Span* it = list.Begin();
while (it != list.End())
{
if (it->_freeList != nullptr) //span下面有对象
{
return it;
}
else //找下一个span
{
it = it->_next;
}
}

// 先把central cache的桶锁解掉,这样如果其他线程释放内存对象回来,不会阻塞
list._mtx.unlock();

// 走到这里说没有空闲span了,只能找page cache要
PageCache::GetInstance()->_pageMtx.lock();
Span\* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
span->_isUse = true;
span->_objSize = size;
PageCache::GetInstance()->_pageMtx.unlock();

// 对获取span进行切分,不需要加锁,因为这会其他线程访问不到这个span

// 计算span的大块内存的起始地址和大块内存的大小(字节数)
char\* start = (char\*)(span->_pageId << PAGE_SHIFT);
size_t bytes = span->_n << PAGE_SHIFT;
char\* end = start + bytes;

// 把大块内存切成自由链表链接起来
// 1、先切一块下来去做头,方便尾插
span->_freeList = start;
start += size;
void\* tail = span->_freeList;
int i = 1;
while (start < end)
{
	++i;
	NextObj(tail) = start;
	tail = NextObj(tail); // tail = start;
	start += size;
}

NextObj(tail) = nullptr;

// 1、条件断点
// 2、疑似死循环,可以中断程序,程序会在正在运行的地方停下来
//int j = 0;
//void\* cur = span->\_freeList;
//while (cur)
//{
// cur = NextObj(cur);
// ++j;
//}

//if (j != (bytes / size))
//{
// int x = 0;
//}

// 切好span以后,需要把span挂到桶里面去的时候,再加锁
list._mtx.lock();
list.PushFront(span);

return span;

}

// 从中心缓存获取一定数量的对象给thread cache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
//算出是哪个桶的,size是单个对象大小
size_t index = SizeClass::Index(size);
_spanLists[index]._mtx.lock(); //加锁

// 获取一个非空的span
Span\* span = GetOneSpan(_spanLists[index], size);
assert(span);
assert(span->_freeList);

// 从span中获取batchNum个对象
// 如果不够batchNum个,有多少拿多少
start = span->_freeList;
end = start;
size_t i = 0;
size_t actualNum = 1;   //actualNum实际获取的对象
//往后走batchNum - 1步
while ( i < batchNum - 1 && NextObj(end) != nullptr)
{
	end = NextObj(end);
	++i;
	++actualNum;
}
span->_freeList = NextObj(end);
NextObj(end) = nullptr;
span->_useCount += actualNum;

 条件断点
int j = 0;
void\* cur = start;
while (cur)
{
	cur = NextObj(cur);
	++j;
}

if (j != actualNum)
{
	int x = 0;
}

_spanLists[index]._mtx.unlock();

return actualNum;

}

void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
//先找到属于哪个桶
size_t index = SizeClass::Index(size);
_spanLists[index]._mtx.lock();
//遍历list
while (start)
{
void* next = NextObj(start);

	Span\* span = PageCache::GetInstance()->MapObjectToSpan(start);
	NextObj(start) = span->_freeList;
	span->_freeList = start;
	span->_useCount--;

	// 说明span的切分出去的所有小块内存都回来了
	// 这个span就可以再回收给page cache,pagecache可以再尝试去做前后页的合并
	if (span->_useCount == 0)
	{
		_spanLists[index].Erase(span);  //拿出span,此时span里的小块内存都是乱序的
		span->_freeList = nullptr;      //将span里的小内存地址都置空
		span->_next = nullptr;
		span->_prev = nullptr;

		// 释放span给page cache时,使用page cache的锁就可以了
		// 这时把桶锁解掉
		_spanLists[index]._mtx.unlock();

		PageCache::GetInstance()->_pageMtx.lock();
		PageCache::GetInstance()->ReleaseSpanToPageCache(span);
		PageCache::GetInstance()->_pageMtx.unlock();

		_spanLists[index]._mtx.lock();
	}

	start = next;
}

_spanLists[index]._mtx.unlock();

}


### 高并发内存池–page cache


**申请内存:**


1. 当central cache向page cache申请内存时,**page cache先检查对应位置有没有span,如果没有则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是4页page,4页page后面没有挂span,则向后面寻找更大的span,假设在10页page位置找到一个span,则将10页pagespan分裂为一个4页page span和一个6页page span。**
2. 如果找到\_spanList[128]都没有合适的span,则向系统使用mmap、brk或者是VirtualAlloc等方式申请128页page span挂在自由链表中,再重复步骤1中的过程。
3. 需要注意的是central cache和page cache 的核心结构都是spanlist的哈希桶,但是他们是有本质区别的,central cache中哈希桶,是按跟thread cache一样的大小对齐关系映射的,他的spanlist中挂的span中的内存都被按映射关系切好链接成小块内存的自由链表。而page cache 中的spanlist则是按下标桶号映射的,也就是说第i号桶中挂的span都是i页内存。


![img](https://img-blog.csdnimg.cn/img_convert/aaf475ddd6801c163a554b81cc871af2.png)
![img](https://img-blog.csdnimg.cn/img_convert/234fc46b8d0fd6b58f0fcd8bb302239d.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

128]都没有合适的span,则向系统使用mmap、brk或者是VirtualAlloc等方式申请128页page span挂在自由链表中,再重复步骤1中的过程。
3. 需要注意的是central cache和page cache 的核心结构都是spanlist的哈希桶,但是他们是有本质区别的,central cache中哈希桶,是按跟thread cache一样的大小对齐关系映射的,他的spanlist中挂的span中的内存都被按映射关系切好链接成小块内存的自由链表。而page cache 中的spanlist则是按下标桶号映射的,也就是说第i号桶中挂的span都是i页内存。


[外链图片转存中...(img-WDw6LYId-1715619581300)]
[外链图片转存中...(img-wfVxb4XK-1715619581300)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值