高并发内存池:CentralCache核心设计

CentralCache是高并发内存池三层结构中的第二层,承担着承上启下的关键作用。它作为所有线程共享的缓存,负责平衡各个线程的内存使用,并在ThreadCache需要时提供内存块。

1. 🏗️ CentralCache 的整体结构与作用

CentralCache 的核心作用是:

  • 共享资源:所有线程的ThreadCache在需要时都会向CentralCache申请或归还内存。
  • 减少锁竞争:虽然CentralCache是共享的,需要加锁,但采用了桶锁(每个哈希桶一个锁),而非全局锁,从而大幅减少锁竞争。
  • 内存中转与平衡:从PageCache申请大块内存(Span),并切割成小块分配给ThreadCache;同时回收ThreadCache归还的内存,并在适当时机将空闲的Span交还PageCache进行合并,缓解内存碎片问题。

CentralCache 同样采用哈希桶结构,其映射规则与ThreadCache完全一致(如256KB以内,使用相同的对齐规则和桶数量)。但与ThreadCache每个桶挂的是自由链表不同,CentralCache的每个桶挂的是一个带头双向循环链表,链表中每个节点是一个Span对象。

2. 📦 Span 结构设计

Span是管理连续大块内存的基本单位,通常以页(如4KB或8KB)为单位。它在CentralCache和PageCache中至关重要。

struct Span {
    PAGE_ID _pageId = 0;// 起始页号,用于标识和合并
    size_t _n = 0;// 该Span管理的页数

    Span* _next = nullptr;// 双向链表指针
    Span* _prev = nullptr;

    void* _freeList = nullptr;// 指向由该Span分割出来的小块内存自由链表
    size_t _useCount = 0;// 记录已被分配出去的小块内存数量
    size_t _objSize = 0;// 该Span分割出的小块内存的大小// ... 可能还有其他字段,如用于标记是否正在使用等
};

Span的核心思想是将从PageCache拿到的大块连续内存(N页),根据其所在桶的要求,切割成一个个大小为_objSize的小内存块。这些小块内存用自由链表连接起来,_freeList指向链表头。_useCount为0时,表示所有小块内存都已归还,整个Span可以归还给PageCache。

3. 🔒 单例模式与桶锁

CentralCache在整个进程中只有一个实例,因此通常采用单例模式(饿汉模式) 实现。

class CentralCache {
public:
    static CentralCache* GetInstance() {
        return &_sInst;
    }
// ... 其他成员函数,如FetchRangeObj, ReleaseListToSpansprivate:
    SpanList _spanLists[NFREELIST];// 哈希桶数组,与ThreadCache桶数一致private:
    CentralCache() {}// 构造函数私有化CentralCache(const CentralCache&) = delete;
    static CentralCache _sInst;// 静态实例
};

每个SpanList(哈希桶)都有自己的锁(std::mutex),即桶锁。当多个ThreadCache同时访问不同的桶时不会阻塞,大大提升了并发性能。

4. ⚙️ CentralCache 核心操作

4.1 从 CentralCache 获取内存(FetchRangeObj

当ThreadCache的某个桶为空时,会调用FetchFromCentralCache,进而调用CentralCache的FetchRangeObj方法。

  1. 查找Span:遍历对应桶的Span链表,找到一个有空闲内存块(_freeList不为空)的Span。
  2. 批量获取:从找到的Span中,批量获取一定数量的小内存块。数量采用慢开始反馈调节算法,初始值较小,根据该ThreadCache以往的需求动态增加,避免一次分配过多用不完。
  3. 返回内存块:将这批内存块的首尾指针返回给ThreadCache,ThreadCache将其挂接到自己的自由链表上。
  4. 更新计数:增加Span的_useCount
  5. 申请新Span:如果该桶所有Span都无空闲块,则向PageCache申请一个新的Span,进行切割并加入该桶,再从新的Span中分配。

4.2 向 CentralCache 归还内存(ReleaseListToSpans

当ThreadCache某个链表过长或线程销毁时,会将内存块归还给CentralCache。

  1. 找到对应Span:根据内存块地址和页号映射关系,确定该内存块属于哪个Span。
  2. 头插归还:将内存块头插回其所属Span的_freeList
  3. 更新计数:减少Span的_useCount
  4. Span回收:如果某个Span的_useCount减为0,说明所有小块内存都已归还。此时CentralCache会将该Span从对应的桶链表中摘除,并归还给PageCache。PageCache会尝试合并相邻的空闲Span,组成更大的连续内存块,解决外部碎片问题。

5. 💎 总结与下一篇预告

CentralCache 通过桶锁Span管理,在多线程环境下高效地协调了内存的分配与回收,既减少了锁竞争,又为PageCache的内存合并提供了基础。

下一篇我们将深入最底层——PageCache(页缓存)。它将揭秘:

  • 更大粒度的管理:PageCache如何以Span为单位管理从系统申请的大内存。
  • 内存合并的奥秘:PageCache如何通过前后页合并来有效解决外部碎片问题。
  • 全局唯一的单例:PageCache同样采用单例模式,是整个内存池的后备仓库。

CentralCache 的成功设计使得高并发内存池在保证多线程性能的同时,也能有效地管理内存碎片,是现代高性能C++程序中不可或缺的组件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值