项目日记---高并发内存池整体框架
内存池整体结构
首先,我们的内存池采用了三层缓存结构来实现不同的功能,每一层直接都紧密连接。
三层分别是:
- ThreadCache(线程缓存结构)
- CentralCache(中心缓存结构)
- PageCache(页缓存结构)
接下里我们简单介绍一下这几个结构
ThreadCache:线程缓存是每个线程独享的缓存结构,线程对内存块的申请和释放都在这个缓存中进行,用于小于256kb的内存块的分配,由于每个线程独享一个ThreadCache结构,所以对这个结构的操作是无需加锁的。
CentralCache:中心缓存是所有线程共享的缓存结构,当ThreadCache中没有内存可供使用时,便向CentralCache申请内存,同时CentralCache会在合适的时机回收ThreadCache所申请的内存块,避免一个线程占用太多内存而其他线程无内存可用,达到内存均衡调度的目的。由于多个线程公用一个CentralCache,所以会存在竞争问题,为了避免竞争需要加锁,这里采用桶锁,即对于不同大小的内存对象的申请都有各自的桶锁,其次只用在ThreadCache中没有内存对象可以使用时才会申请,所以这里的锁竞争一般不会很激烈。
PageCache:
page cache中存储的内存是以页为单位进行存储及分配的,当central cache需要内存时,page cache会分配出一定数量的页分配给central cache,而当central cache中的内存满足一定条件时,page cache也会在合适的时机对其进行回收,并将回收的内存尽可能的进行合并,组成更大的连续内存块,缓解内存碎片的问题。
以上只是简单的介绍,并不能帮助我们完全理解这三个结构,后续我会给出详细讲解。
三层缓存的结构:
各个部分的主要作用
thread cache主要解决锁竞争的问题,每个线程独享自己的thread cache,当自己的thread cache中有内存时该线程不会去和其他线程进行竞争,每个线程只要在自己的thread cache申请内存就行了。
central cache主要起到一个居中调度的作用,每个线程的thread cache需要内存时从central cache获取,而当thread cache的内存多了就会将内存还给central cache,其作用类似于一个中枢,因此取名为中心缓存。
page cache就负责提供以页为单位的大块内存,当central cache需要内存时就会去向page cache申请,而当page cache没有内存了就会直接去找系统,也就是直接去堆上按页申请内存块。
thread cache结构:
如图所示,threadcache是一个哈希桶的结构,每个桶后挂着的都是大小相同的内存对象,也就是每个桶都是一个自由链表。
如何申请内存?
申请内存首先要做的就是内存对齐,比如线程1申请6字节的内存,但是threadcache中并没有6字节的哈希桶,于是便按照规则进行对齐到8字节,threadcache在8字节的哈希桶中查找是否存在内存对象,存在则返回不存在则向centralcache申请。
如何释放内存?
释放内存也很简单,对照定长内存池的自由链表,释放回来的内存对象根据大小挂到对应大小的桶的自由链表里供重复使用。如果某个桶的自由链表中内存对象足够多则会将部分内存还给centralcache。
central cache结构:
同样也是哈希桶,但是和threadcache稍有不同。每个桶后面挂的不是单个内存对象而是一个个span组成的链表,每个span按照对应桶的大小被切分为一个个小的内存对象,例如8字节桶中,每个span被切割成若干8字节的内存对象,其他桶同理。
每个span管理的都是一个以页为单位的大块内存,每个桶里面的若干span是按照双链表的形式链接起来的,并且每个span里面还有一个自由链表,这个自由链表里面挂的就是一个个切好了的内存块,根据其所在的哈希桶这些内存块被切成了对应的大小。
pagecache结构:
pagecache虽然也是哈希桶结构,但是桶的下标不同,每个下标表示桶内的span对象有多少页。
pagecache是服务于centralcache的所以这里的span并没有被切分为小块内存,当centralcache没有内存时向pagecache申请的就是某一个固定页数的span,而如何切分申请到的span由centralcache自己决定。
至于page cache当中究竟有多少个桶,这就要看你最大想挂几页的span了,这里我们就最大挂128页的span,为了让桶号与页号对应起来,我们可以将第0号桶空出来不用,因此我们需要将哈希桶的个数设置为129。
总结
这整个三层结构的设计是十分巧妙的,在线程缓存中是不需要加锁的,因为每个线程独享这个结构,这也是这个项目比较快的原因之一,在中心缓存中也不需要完全加锁,而是使用桶锁,只有当不同的线程进入到同一个桶时才会有锁互斥!