高并发内存池 - page cache 整体设计
高并发内存池 - page cache 整体设计项目 gitee 链接 高并发内存池项目项目 github 链接 高并发内存池项目这一层来到Page Cache我们将对其的整体设计进行解析。首先还是以SpanList为基本结构但与Central Cache有几处不同。它不再保持和Thread Cache的映射关系而是根据Span中存储的页数进行划分将有着相同页数大小的Span划分到了一个SpanList。将结构明晰后我们梳理一下申请内存的逻辑当Central Cache向Page Cache申请内存时Page Cache先检查对应位置有没有Span如果没有则向更⼤⻚寻找⼀个Span如果找到则分裂成两个。⽐如申请的是 4 ⻚ page4 ⻚ page 后⾯没有挂Span则向后⾯寻找更⼤的Span假设在 10 ⻚ page 位置找到⼀个Span则将 10 ⻚page Span分裂为⼀个 4 ⻚page span和⼀个 6 ⻚page span。如果找到_spanList[128]都没有合适的Span则向系统使⽤ mmap、brk 或者是 VirtualAlloc 等⽅式申请 128 ⻚page Span挂在⾃由链表中再重复 1 中的过程。需要注意的是Central Cache和Page Cache的核⼼结构都是Spanlist的哈希桶但是他们是有本质区别的Central cache中哈希桶是按跟Thread Cache⼀样的⼤⼩对⻬关系映射的他的Spanlist中挂的Span中的内存都被按映射关系切好链接成⼩块内存的⾃由链表。⽽Page Cache中的Spanlist则是按下标桶号映射的也就是说第 i 号桶中挂的 span 都是 i ⻚内存。释放内存如果Central Cache释放回⼀个Span则依次寻找Span的前后Page id的没有在使⽤的空闲Span看是否可以合并如果合并继续向前寻找。这样就可以将切⼩的内存合并收缩成⼤的Span减少内存碎⽚。在讲解完申请内存和释放内存的逻辑后有几个设计中的要点需要重点讲解锁设计在哪里在剖析完申请内存后会发现是有多个线程向Page cache申请内存的可能的但好像都只是在各自的桶内申请所以只需要设计桶锁就够了么答案显然是否定的。问题就在Page Cache如果桶中没有Span后会尝试分割更大的Span然后将分割完后的Span放入对应的桶中这说明申请内存时并非是桶与桶独立的而是会产生交集这样就可能产生死锁情况假设线程 A 申请 1 页但Page Cache的SpanList[1]为空。它只能去更大的桶找比如SpanList[8]里有一个 8 页的Span。分割流程从SpanList[8]取出这个 8 页Span切分成 1 页 7 页把 1 页返回给上层把剩余的 7 页 挂回SpanList[7]如果此时只锁SpanList[8]的桶锁步骤 1 可以安全取出锁住了但步骤 4 需要把 7 页挂到SpanList[7]这需要另一把锁SpanList[7]._bucketMtx此时就会面临两个致命问题死锁风险如果线程 B 同时申请 7 页发现SpanList[7]为空去SpanList[8]找——两把锁的获取顺序相反经典死锁。竞态条件即使你用固定顺序加锁避免死锁切分过程中的状态对其他线程是可见的。看这个时间线时间线程 A申请 1 页线程 B申请 7 页T1拿到SpanList[8]桶锁取出 8 页 SpanT2释放SpanList[8]桶锁已经取出来了T3开始切分此时 8 页 Span 已不在任何桶里T4检查SpanList[7]为空T5检查SpanList[8]也为空因为被 A 取走了T6线程 B 认为系统没有 7 页内存去向系统申请brk/mmapT7线程 A 切分完把 7 页挂到SpanList[7]结果线程 B 白白向 OS 申请了一块新内存而实际上线程 A 马上就会释放一个 7 页Span到SpanList[7]。这导致内存碎片增加、系统调用开销翻倍、Page Cache失去了缓存合并的意义。所以不推荐使用桶锁的底层原因是什么呢桶锁的粒度太小无法把跨桶的切分-迁移包装成一个原子事务。如果可以包装成一个原子事务就不会出现上述的致命问题所以对于Page Cache要设计一把大锁。Page Cache可以采用什么模式设计这里其实算是一个知识点的深入和拓展借着Page Cache抛砖引玉出来。对于一个进程Page Cache只有一个不会产生多个所以可以思考到使用单例模式来实现单例模式有两种实现方法饿汉模式懒汉模式最终我们选择饿汉模式因为选择懒汉模式会产生几个较大的问题。1. 指令重排问题对于new PageCache一行代码编译器会将其拆分成三步分配内存_sInstnewPageCache();在内存上构造对象PageCache*objnew(raw)PageCache();// 调用构造函数把地址赋值给指针_sInstobj;这三个步骤在 C 的抽象机器里是有先后顺序的但编译器和 CPU 为了优化性能可能把步骤 3 重排到步骤 2 之前。编译器认为“new PageCache()的返回值最终就是要给_sInst的而构造函数里如果没有用到_sInst那先把地址写进去再执行构造似乎也没问题”于是可能生成这样的机器码1. call operator new ; 分配内存地址暂存寄存器 eax 2. mov [_sInst], eax ; ⚠️ 先把地址写入 _sInst步骤3提前了 3. call PageCache::ctor ; 调用构造函数步骤2被延后了即使编译器没重排现代 CPUx86、ARM为了提高流水线效率也会乱序执行Out-of-Order Execution。只要两个指令没有数据依赖CPU 可能先执行mov [_sInst], eax再执行构造函数。2. 时序推演问题假设有两个线程 A 和 B 同时调用GetInstance()GetInstance() 函数的功能是获取单例模式中对象的地址。staticPageCache*GetInstance(){if(_sInstnullptr){// 第一次检查无锁std::lock_guardstd::mutexlock(_mtx);if(_sInstnullptr){// 第二次检查有锁_sInstnewPageCache();// 危险区}}return_sInst;}时间线程 A线程 BT1第一次检查_sInst nullptr✅T2获取锁_mtxT3第二次检查_sInst nullptr✅T4执行new分配内存T5⚠️指令重排把地址写入_sInst构造还没完成T6第一次检查_sInst ! nullptr❌T7直接返回_sInstT8线程 B 拿到指针调用成员函数…T9崩溃对象还没构造完vtable 未初始化、成员变量是垃圾值T10调用构造函数完成正是因为new PageCache()不是原子操作编译器可能重排指令导致其他线程拿到构造了一半的指针先赋值地址再执行构造函数。当然由于 C11 新特性的引入这个问题已经能用新的方法来解决但这个问题还是要在此提出作者觉得这个问题还是很好的有助于能力的提升。关于Page Cache的整体框架分析就到此结束了下面直接展示现阶段可以实现的代码部分其余部分后续篇章会进行讲解。Page Cache.h#pragmaonce#includeComm.hclassPageCache{public:staticPageCache*GetInstance(){return_sInst;}Span*NewSpan(size_t k);//要的 Span 长度为几页private:std::mutex _pageMtx;//桶锁在 PageCache 中是行不通的需要整体上锁SpanList _spanLists[NPAGES];PageCache(){}PageCache(constPageCache)delete;staticPageCache _sInst;};Page Cache.cpp#includePageCache.hstaticPageCache _sInst;静态成员变量的初始化要放在 .cpp 文件中放入头文件中会被重复包含引发链接报错。本篇文章到此结束让我们下篇文章再见

相关新闻

终极免费OCR工具:Umi-OCR如何彻底解决PDF文字无法复制的难题?

终极免费OCR工具:Umi-OCR如何彻底解决PDF文字无法复制的难题?

终极免费OCR工具:Umi-OCR如何彻底解决PDF文字无法复制的难题? 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二…

2026/7/24 21:38:48阅读更多 →
Windows键盘重映射终极指南:如何用SharpKeys实现个性化键盘布局

Windows键盘重映射终极指南:如何用SharpKeys实现个性化键盘布局

Windows键盘重映射终极指南:如何用SharpKeys实现个性化键盘布局 【免费下载链接】sharpkeys SharpKeys is a utility that manages a Registry key that allows Windows to remap one key to any other key. 项目地址: https://gitcode.com/gh_mirrors/sh/sharpke…

2026/7/24 21:38:48阅读更多 →
NVIDIA Profile Inspector完整教程:解锁显卡隐藏设置的终极指南

NVIDIA Profile Inspector完整教程:解锁显卡隐藏设置的终极指南

NVIDIA Profile Inspector完整教程:解锁显卡隐藏设置的终极指南 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector NVIDIA Profile Inspector是一款功能强大的开源工具,它能让你访问…

2026/7/24 21:38:48阅读更多 →
5分钟破解Adobe全家桶:GenP 3.0全版本激活指南

5分钟破解Adobe全家桶:GenP 3.0全版本激活指南

5分钟破解Adobe全家桶:GenP 3.0全版本激活指南 【免费下载链接】Adobe-GenP Adobe CC 2019/2020/2021/2022/2023 GenP Universal Patch 3.0 项目地址: https://gitcode.com/gh_mirrors/ad/Adobe-GenP 你是否因为Adobe Creative Cloud高昂的订阅费用而无法使用…

2026/7/24 23:01:04阅读更多 →
实用高效的Windows驱动管理神器:Driver Store Explorer完全指南

实用高效的Windows驱动管理神器:Driver Store Explorer完全指南

实用高效的Windows驱动管理神器:Driver Store Explorer完全指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 您是否曾因Windows系统盘空间不足而烦恼?是否发现…

2026/7/24 23:01:04阅读更多 →
Centos7 编译安装Cmake+ffmpeg

Centos7 编译安装Cmake+ffmpeg

1、系统准备安装好Centos7.9,并打好系统补丁,安装一些常用工具,关闭防火墙,yum -y update yum -y install git yum -y install vim yum -y install unzip yum -y install net-tools yum -y install wget yum -y install openssl-d…

2026/7/24 23:01:04阅读更多 →
Legacy iOS Kit终极指南:让经典iOS设备重获新生的完整工具链

Legacy iOS Kit终极指南:让经典iOS设备重获新生的完整工具链

Legacy iOS Kit终极指南:让经典iOS设备重获新生的完整工具链 【免费下载链接】Legacy-iOS-Kit An all-in-one tool to restore/downgrade, save SHSH blobs, jailbreak legacy iOS devices, and more 项目地址: https://gitcode.com/gh_mirrors/le/Legacy-iOS-Kit…

2026/7/24 23:01:04阅读更多 →
电脑机房用成品——名词解释

电脑机房用成品——名词解释

目录 红黑电源隔离插座 PDU(电源分配单元) 电源ac和dc MTBF:平均无故障时间 关于标准机箱、机柜 红黑电源隔离插座 红黑电源隔离插座简称“红黑插座”,是中国国内达到国家保密标准GB2099.3-1997《红黑电源滤波隔…

2026/7/24 23:01:04阅读更多 →
5分钟零基础AI换脸指南:roop-unleashed让你轻松成为面部替换专家

5分钟零基础AI换脸指南:roop-unleashed让你轻松成为面部替换专家

5分钟零基础AI换脸指南:roop-unleashed让你轻松成为面部替换专家 【免费下载链接】roop-unleashed Evolved Fork of roop with Web Server and lots of additions 项目地址: https://gitcode.com/gh_mirrors/ro/roop-unleashed 还在为复杂的深度学习训练望而…

2026/7/24 22:59:04阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 0:58:53阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 0:58:53阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/24 0:58:53阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:06阅读更多 →
【LeetCode 54】螺旋矩阵

【LeetCode 54】螺旋矩阵

问题描述: 解法: 1、模拟(参考自【LeetCode 54】螺旋矩阵-CSDN博客) int *spiralOrder(int **matrix, int matrixSize, int *matrixColSize, int *returnSize) {static const int dirs[4][2] {{0, 1}, {1, 0}, {0, -1}, {-1, …

2026/7/24 0:00:06阅读更多 →
2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

知春路不相信模型领先今年WAIC大会,昔日AI六小龙来了五家,分别是Kimi、阶跃星辰、Minimax、百川智能、零一万物。连放弃基模的百川和零一万物都来了,唯一缺席的竟是近几个月来风光无限的智谱。(DeepSeek一直不参加)WAI…

2026/7/24 0:00:06阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/24 23:01:03阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/24 19:00:40阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/24 19:00:40阅读更多 →