当前位置: 首页 > news >正文

深入浅出高并发内存池:原理、设计与实现

一.引言

传统内存分配的痛点​​:

1.对于一些操作系统封装的malloc可能对于多线程并发场景下性能较差

2.频繁申请释放不同大小的内存,导致大量内存碎片,降低内存利用率

所以针对以上问题

本项目采用了池化技术,像一些连接池,线程池,对象池。

内存池:由我们自己负责管理一块大块内存,当我们需要的时候通过自己管理的接口进行内存的申请和释放,相比于传统的内存申请和释放来言,一方面因为一次申请大批内存,减少了系统调用以及,归还不连续内存块导致的内存碎片问题。

tcmalloc: TCMalloc (google-perftools) 是用于优化C++写的多线程应用,比glibc 2.3的malloc快。这个模块可以用来让MySQL在高并发下内存占用更加稳定。

二.高并发内存池的整体架构

1.三层架构

本项目采用三层架构

Thread Cache(线程缓存):线程独享,并且是无锁操作

Central Cache(中心缓存):所有线程共享,一方面给Thread Cache切好的小块批量内存块,另一方面,将归还的小块内存整体管理,如果达到回收条件,归还给PageCache。

Page Cache(页缓存):所有线程共享,管理更大的内存,通常以页为单位,当Central Cache需要内存,通过Page Cache系统申请大块内存,并且大块内存切好后给予Central Cache。对于归还的内存块,首先还给对应的页

1).Thread Cache

结构示意图

ThreadCache的数据结构是哈希桶,每一桶管理一个固定大小的自由链表。并且是无锁结构的。

假设需要8个字节的内存,首先先去Thread Cache通过哈希找到哈希桶中对应自由链表的位置,然后将其取出一块。

但是我们可以看到并不是每个大小的内存都有对应的链表,这是因为如果给每个大小的的内存都有对应的链表的话,那么这个桶的大小可能会非常的大,从结构图可以看到会有256*1024个自由链表,所以为了解决这个问题:

采用内存对齐的方式:

1.首先保证最小大小必须是一个指针大小(64位),为了保证能够存储下一个结点的地址,如下图。

2.保证内存向上对齐(比如你申请的大小是5字节)的同时减少内存碎片所以

    // [1,128]                       8byte对齐         freelist[0,16)
    // [128+1,1024]                  16byte对齐        freelist[16,72)
    // [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)  

按照以下规则进行内存对齐,保证的桶数目的同时一定程度减少内碎片,将其控制在了10%

为什么可以保证无锁呢?

通过TLS(Thread Local Shorage)我们可以保证无锁。

Thread Local Storage (TLS) 是 C++ 中一项重要的多线程编程特性,它允许每个线程拥有某个变量的独立副本,从而避免数据竞争和锁开销。

简单一句话,保证每个线程都独立的创建这个被标记为TLS的变量。

所以对于多线程而言,TLS保证了线程在数据的私有。

2).Central Cache

Central Cache更像是Thread Cache 和 Page Cache的过度阶段,就好比是一个缓冲区对上(Page Cache)申请以Span为的单位(管理多个页),对下(Thread Cache)给予批量内存块。

CentralCache的桶的结构跟ThreadCache一样,但是自由链表管理的结点变成了Span

Span可以理解为管理多个内存块的双向链表结构

那么Central Cache有什么特点呢?

相比于ThreadCache的无锁结构,Central Cache因为是唯一的,所以需要加锁,但是只需要桶锁即可,因为归还的内存块大小确定只需要找到响应的索引,并将其对应的自由链表加锁即可。

对于ThreadCache而言,如果自己的管理的内存块超过通过Central Cache获得的批量内存块数量时就将其归还给CentralCache,并把每个内存块归还给对应的Span(为什么可以找到对应的Span呢?看下文的Page Cache部分)。当Central Cache中某个Span分配给Thread Cache所有内存块都归还时,将Span归还给PageCache

而ThreadCache获取内存块的时候,Central Cache如何知道该给多少块内存块呢?

这个时候我们采用慢启动(在一定程度上,如果某大小的内存块被多次申请,Central Cache一次给予的批量内存块数量会随着申请次数的增加而增加)的方式来获取,一方面保证了效率,另一方面保证,给予的内存块得到更大程度的利用。

3).Page Cache

PageCache的数据结构也是哈希桶,每个自由链表管理相应大小的Span双向链表。

大小以page为单位,也就是页

Span管理连续内存的起始地址(页号 >> 页大小),页的数量。

大概就类似如下图的效果

对于Central Cache申请的内存,首先需要通过Central Cache申请内存块的数量以及内存块的大小来知道需要几页的连续内存,接着将这个Span给CentralCache,CentralCache将这个连续内存切成小块,放到链表中,虽说是链表,但实际物理上是连续的。将内存块的空间的前8个byte指向下一个内存块。如果PageCache没有对应大小页的Span,会选择一个有多页的大块Span并将其切成目标Span,以及剩余的页搞成Span放回对应的桶。

 对于Thread Cache归还的批量内存块,Central Cache可以通过其内存块的地址来找到对应的Span位置,因为在对小块内存块取余PAGE_SIZE获得的是页号,在PageCache向系统申请内存的时候会把每个页对应的Span记录到页号与Span的映射。当对应的Span得到归还,会将把该Span的前后进行合并(因为可能是一个大的Span切成小的Span)。

http://www.dtcms.com/a/411997.html

相关文章:

  • 0926第一个口头OC——快手主站前端
  • 网站职业技术培训学校广告设计公司深圳策划设计公司
  • A股大盘数据-20250926分析
  • 振动力学|01 单自由度系统的振动分析
  • 【Luogu_P2184】 贪婪大陆【树状数组】
  • 太原网站制作网站建设相关岗位名称
  • phpstorm content.dat.storageData 文件解析:作用、风险与处理建议
  • 做网站要学一些什么一学一做教育视频网站有哪些内容
  • 基于华为openEuler部署Blog轻量级博客系统
  • 西安企业建站费用互联网营销外包推广
  • 怎样做平台网站制作表白网页
  • 【系统架构设计(37)】数据库体系结构
  • 八爪鱼网络网站建设哪里有做商城的网站
  • 网站历史记录怎么恢复百度推广服务
  • 服装设计素材网站网站维护工程师
  • 如何快速查找并解除端口占用:完整指南
  • 【给蔡磊】基因编辑根治渐冻症
  • 如何掘金西方游戏市场?从玩家付费偏好解析2025出海品类与商业化模式
  • 平面设计师上的网站wordpress调用内容
  • 宁波比较好的网站公司中国建设业管理协会网站
  • SCADA系统集成WebSocket实战:构建实时可视化管理看板
  • 深圳网站制作建设公司网站给篡改了要怎么做
  • 什么是响应式网站建设英文公司网站制作
  • 外贸商城网站 定制凡客精选带货达人
  • wordpress在线代码编辑关键词首页排名优化价格
  • numpy -- 数组操作 之 连接数组
  • C# 主窗口 单例 唯一进程 互斥锁
  • 重庆网站建设开发公司做网站如何把支付宝微信吧
  • 【Mysql中的锁机制二】
  • 解决微信小程序真机调试中访问本地接口 localhost:8080 报错