03 中心资源层的初步设计

         由于本人发现,项目每推进一步的时候,往往还要对之前已经完成的部分进行修改 ,因此以后本栏目文章将不会再向02节那样详细得讲每一个功能模块了,只记录关键之处(项目的完整框架及代码,后续会陆续上传到本人的Gitee仓库)。       

        中心资源层的逻辑结构和线程资源层几乎是一样的,只不过是多个线程共享使用中心资源层。

一、Span块

        中心资源层也是采用桶链来存放内存块的,但是比线程资源层的桶链多了一层,线程资源层位桶➡内存块结构,而中心资源层为桶➡内存包(Span)➡内存块的结构,如图所示:

         Span包的作用是管理以页为单位的大块内存,因为中心资源层的资源是来自于页缓存资源层的,页缓存资源层当然就是以页为单位向中心资源层提供内存资源,由于这些内存空间,既要被分配给线程资源层,也要考虑如何向线程资源层回收,还要考虑如何把Span包内的资源还给更底层的页缓存资源层,所以设计一个复杂的管理结构,就十分有必要了,而不是像线程资源层一样”桶➡内存块“简单的二维结构就完事了。
目前Span的结构(在实现页缓存资源层后还需要进行补充):
//内存包span,里面包含了连续页的内存,是中心资源层和页缓存资源层的重要结构
struct Span
{
	PAGE_ID pageID ;//包中起始页在用户内存地址空间中的页编号!
	size_t counts ;//包中页的数量
	Span* prev ;
	Span* next ;

	void* freelist ;//老成员,桶类,里面存放着span内存包中被切好的内存块
	size_t usedBlocks ;//已经被分配出去的内存块数量
};

(pageID在页缓存资源层极为重要,它实际上才在页缓存资源层标识了span包中内存块的起始地址,而freelist在底层实际上是空置的,只有其上交给了中心资源层,才会根据pageID计算出第一个内存块的地址)

而SpanList桶实际就是一个Span包组成的链表了:

class SpanList
{
public:
	SpanList()
	{
		head = new Span;
		head->prev = head;
		head->next = head;
	}
	void insert(Span*pos,Span*newNode)
	{
		newNode->next = pos;
		newNode->prev = pos->prev;
		pos->prev = newNode;
		newNode->prev->next = newNode;
	}
	void erase(Span* pos)
	{	
		assert(pos!=head);
		pos->prev->next = pos->next;
		pos->next->prev = pos->prev;
	}

	mutex& Getmutex()
	{
		return mt;
	}
private:
	Span* head;
	mutex mt;//中心资源层,每个SpanList链都必须加锁,以防止多个线程一下申请多个同样大小的内存块时(如果是不同大小的内存块,那么就会向不同的中心桶索取,不存在锁竞争的问题)。
};

(注意,开头的图里为了阐明桶的包含关系,没有突出底层的数据结构,这个SpanList是一个双向带头循环链表)。

二、单例模式

       由于是多个线程都会使用同一个中心资源层(CentreCache),因此,根据程序得规范,最好将中心资源层设计成单例模式,以使得整个程序中最多只出现一个中心资源层对象。

饿汉模式:

class CentreCache
{
public:
	static CentreCache& GetCentreCache()
	{
		return self;
	}
	size_t GetRangeMemory(void*&start, void*&end, size_t n, size_t index);//start、end为输出型参数,返回span内存包中分配出去的部分内存块链的起始和尾部,n代表要分配的内存块数量,index代表spanLists链的桶编号(与ThreadCache层的桶的编号是对应的),返回值为实际分配出去的内存块数量
	Span* GetOneSpan(SpanList* sl, size_t size);
private:
	CentreCache() {};
	CentreCache(const CentreCache&) = delete;
	CentreCache& operator=(const CentreCache&) = delete;
private:
	SpanList spanLists[BUCKETSIZE];
	static CentreCache self;
};
CentreCache CentreCache::self;//必须放在.cpp文件中,如放在.h头文件中会导致重复定义的问题
size_t CentreCache::GetRangeMemory(void*& start, void*& end, size_t n, size_t index)
{
	spanLists[index].Getmutex().lock();//加上桶锁
	Span* sp = GetOneSpan(&spanLists[index], index,alignedSize);
	assert(sp&&sp->freelist!=nullptr);

	size_t num = n-1;//end要下走的步数
	start = sp->freelist;
	end = start;
	while (num > 0 && NextNode(end) != nullptr)
	{
		--num;
		end = NextNode(end);
	}
	sp->freelist = NextNode(end);
    sp->usedBlocks += n - num;//n-num即为实际分配出去的内存块数量
	spanLists[index].Getmutex().unlock();
	return n - num;//n-num即为actualNumber
}
Span* CentreCache::GetOneSpan(SpanList* sl, size_t index, size_t alignedSize)//从中心资源层找到一个符合线程资源层所申请的Span包,如没有,向页缓存资源层申请一个新的Span块
{
	//后续实现
	return nullptr;
}

        注意,中心资源层的每个桶可能同时被多个线程的ThreadCache访问,因此必须加锁,由于锁是加在每个桶里的,因此被称为桶锁,当多个线程虽然同时访问中心资源层,但若访问的不是同一个桶,那么对效率的影响微乎其微。

三、02节中还未完成的RequestFromCentralCache函数

        RequestFromCentralCache函数属于线程资源层,作用是向中心资源层申请空间,当中心资源层内存块也不足的时候,会调用中心资源层的 GetOneSpan函数。

         RequestFromCentralCache函数被调用后,在其内部首先应对请求的内存块进行分析,判断一次向中心资源层申请多少内存块好(如果请求多少,就向下申请多少,效率就太低了,应当多申请一些然后将多余的内存块存进线程资源层对应的freelists链表中;但也不宜申请太多,以免浪费)。而项目采用慢开始算法来解决这个问题,随着某个线程请求同一大小的内存块的次数的增多, RequestFromCentralCache函数一次向下申请的内存块数也越来越多,直至达到对应内存块大小设定的硬上限数量。

void* ThreadCache::RequestFromCentralCache(size_t index, size_t alignedSize)
{	
	size_t ReqBlockNum = min(freelists[index].MaxSize, SizeClass::blockCountCal(alignedSize));//慢开始算法,blockCountCal决定每种内存块一次申请的上限
	if (ReqBlockNum == freelists[index].MaxSize)//如果实际申请内存块数等于MaxSize(未达上限),那么MaxSize线性增大,下次再申请同一大小的内存块时,将申请更多
	{
		freelists[index].MaxSize += 2;
	}
	void* start = nullptr;
	void* end = nullptr;
	CentreCache& cenCache = CentreCache::GetCentreCache();
	size_t actualNumber = cenCache.GetRangeMemory(start, end, ReqBlockNum, index);
	assert(actualNumber > 0);
	if (actualNumber >1)
	{
		freelists[index].PushRangeBlock(NextNode(start), end);//如果申请到的内存块数大于1,就将多余的内存块放进桶里去
	}
	return start;
}
//其中freelist类的MaxSize和PushRangeBlock都是在前一章基础上额外增添的内容,这里不作复现,详情见完整项目代码。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值