欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > 【项目日记(5)】第二层:中心缓存的具体实现(上)

【项目日记(5)】第二层:中心缓存的具体实现(上)

2025/1/2 2:32:13 来源:https://blog.csdn.net/2301_77900444/article/details/144652960  浏览:    关键词:【项目日记(5)】第二层:中心缓存的具体实现(上)

目录

  • 前言
  • 1. span结构的具体实现
  • 2. 中心缓存类的定义
  • 3. 中心缓存如何分配小块儿内存?
  • 4. 中心缓存无内存时应该如何做?
  • 5. 总结

前言

中心缓存起到一个承上启下的作用,它负责给线程缓存分配小块儿的内存,并且负责从页缓存申请大块儿内存。

本章重点:

本篇文章着重讲解中心缓存的结构
包括span类的具体成员.并且会讲解
中心缓存是如何给线程缓存分配内存
并且是如何向页缓存申请/内存的!

1. 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; // 是否在被使用。只要pagecache里的span被分给了central cache,就置为true
};// 带头双向循环链表
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 Insert(Span* pos, Span* newSpan){assert(pos);assert(newSpan);Span* prev = pos->_prev;prev->_next = newSpan;newSpan->_prev = prev;newSpan->_next = pos;pos->_prev = newSpan;}void PushFront(Span* span){Insert(Begin(), span);}Span* PopFront(){Span* front = _head->_next;Erase(front);return front;}void Erase(Span* pos){assert(pos);//assert(pos != _head); // 不能删除哨兵位节点// 条件断点// 查看栈帧if (pos == _head){int x = 0;}Span* next = pos->_next;Span* prev = pos->_prev;prev->_next = next;next->_prev = prev;// 注意这里没有直接delete掉这块内存// 只是把它解链了}
private:Span* _head;
public:std::mutex _mtx; // thread cache里的多个线程从central cache同一个桶取出/还回内存块的桶锁
};

对成员变量_useCount的解释:

_useCount是指一个span被切好的内存块分配给threadcache使用的数量。_useCount为0时,代表这个span中所有被分配出去的小块儿内存都被threadcache还回来了,此时可直接将这个span从中心缓存还给页缓存。

2. 中心缓存类的定义

并且,中心缓存整体被设计为了单例模式:
CentralCache.h文件:

// 1.CentralCache也是一个哈希桶结构,里面挂的是一个个大块内存span(页)
// 每个span又被切成了对应ThreadCache的小块内存
// 2.单例饿汉模式
class CentralCache
{
public:static CentralCache* GetInstance(){return &_sInst;}// 获取一个非空的spanSpan* GetOneSpan(SpanList& list, size_t byte_size);// 从中心缓存获取一定数量的对象给thread cachesize_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);// 将一定数量的对象释放到span跨度void ReleaseListToSpans(void* start, size_t byte_size);private:SpanList _spanList[NFREELIST];
private:CentralCache(){}CentralCache(const CentralCache&) = delete;static CentralCache _sInst; // 在类里声明,但是不要在.h文件里定义,在.cpp文件里定义
};

3. 中心缓存如何分配小块儿内存?

FetchRangeObj函数我们并不陌生,在线程缓存中,当桶中没有小块儿内存时就是调用此函数来中心缓存获取的!

分配内存的基本步骤1:

中心缓存会先找到对应的哈希桶,然后去桶中取一个非空的span结构,再将这个span结构中切分好的小块儿内存分配给线程缓存使用

// 从中心缓存获取一定数量的对象给thread cache
// 并且把内存对象从SpanList的桶里给解掉
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{// 先计算出在SpanList的哪个桶里size_t index = SizeClass::Index(size);// thread cache里的多个线程从central cache同一个桶取出/还回内存块的桶锁_spanList[index]._mtx.lock();Span* span = GetOneSpan(_spanList[index], size);// 获取到了一个非空的spanassert(span);assert(span->_freeList);start = span->_freeList;end = start;// 1.end往后走batchNum-1步// 2.当批量获取的大于桶里的,不能越界size_t i = 0;size_t autualNum = 1; // 实际获取的数量while (i < batchNum-1 && NextObj(end) != nullptr){end = NextObj(end);i++;autualNum++;}span->_freeList = NextObj(end);NextObj(end) = nullptr;span->_useCount += autualNum;_spanList[index]._mtx.unlock();return autualNum;
}

4. 中心缓存无内存时应该如何做?

分配内存的基本步骤2:

若对应的哈希桶中的span为空,也就是中心缓存无内存了,就会调用NewSpan去页缓存获取一个新的span结构,然后把新的span切分为小块儿内存后再给线程缓存使用!

// 获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{Span* it = list.Begin();while (it != list.End()){// 查看当前的spanlist中是否还有未分配对象的span// 说明在这个桶中还有小块内存对象,返回进行批量获取if (it->_freeList != nullptr){return it;}else{it = it->_next;}}// 先把central cache的桶锁解掉,这样如果其他线程释放内存对象回来,不会阻塞list._mtx.unlock();// 走到这里说明没有空闲的span了,只能找page cache要PageCache::GetInstance()->_pageMtx.lock();Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));span->_objSize = size;span->_isUse = true; // 只要pagecache里的span被分给了central cache,就置为truePageCache::GetInstance()->_pageMtx.unlock();// 对获取的span进行切分时,不需要加锁,因为这会儿其他线程访问不到这个span// 计算span的大块内存的起始地址和大块内存的大小(字节数)char* start = (char*)(span->_pageId << PAGE_SHIFT); // 起始地址 = 页号<<PAGE_SHIFTsize_t bytes = span->_n << PAGE_SHIFT;char* end = start + bytes; // span的结尾位置// 把大块内存切成自由链表连起来// 1.先切一块下来做头,方便尾插span->_freeList = start;start += size;  // span下挂的是我们需要的大小的一个个映射的小内存块void* tail = span->_freeList;// 把一个个小内存块使用尾插链接到span下while (start < end){NextObj(tail) = start;tail = start; // tail = NextObj(tail)start += size;}NextObj(tail) = nullptr;// 切好span以后,需要把span挂到桶里的时候,再加锁// 避免这个时候有些线程还在取内存list._mtx.lock();list.PushFront(span); // 把span挂到SpanList哈希表中return span;
}

值得注意的是,获取到span后我们要通过这个span的页数来知道这个span有多少内存,并且要通过这个span在程序地址空间的页号来判断这份内存的起始地址是多少!第0页的地址是0000 0000,第一页的地址是8KB,以此类推…

在这里插入图片描述

5. 总结

中心缓存这里给线程缓存分配内存时有两种情况的,当中心缓存无内存时就会向页缓存索要,而本篇文章只讲解了申请内存的过程,而当线程缓存将内存还回来后,还有可能将span还给页缓存。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com