现代很多的开发环境都是多核多线程,在申请内存的场景下,必然存在激烈的锁竞争问题。malloc 本身其实已经很优秀,但是我们项目的原型 tcmalloc 在多线程高并发的场景下更胜一筹,所以这次我们实现的内存池需要考虑以下几方面的问题:
-
性能问题。
-
多线程环境下,锁竞争问题。
-
内存碎片问题。
所以 concurrent memory pool 的主体主要由以下三个部分构成:
-
thread cache:
- 线程缓存是每个线程独有的,用于小于
256KB的内存的分配,线程从这里申请内存不需要加锁,每个线程独享一个cache,这也就是这个并发线程池高效的地方。
- 线程缓存是每个线程独有的,用于小于
-
central cache:
-
中心缓存是所有线程所共享的,
thread cache是按需从central cache中获取的对象。central cache在合适的时机会回收thread cache中的对象,避免一个线程占用了太多的内存,导致其他线程的内存吃紧,达到内存分配在多个线程中更均衡的按需调度的目的。 -
但是因为是所有线程共享的,也就是存在资源竞争的,所以从
central cache取内存对象是需要加锁,不过因为这里用的是桶锁(因为central cache由多个哈希桶组成的,所以不是访问同一个哈希桶的话就不需要加锁),其次只有thread cache的没有内存对象时才会找central cache,所以这里竞争不会很激烈。
-
-
page cache:
- 页缓存是在
central cache缓存上面的一层缓存,存储的内存是以页为单位存储及分配的。当central cache没有内存对象时,会从page cache分配出一定数量的page,并切割成定长大小的小块内存分配给central cache。当一个span的几个跨度页的对象都被回收之后,page cache会回收central cache中满足条件的span对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题。
- 页缓存是在

☢️进一步说明:
每个线程都有一个属于自己的 thread cache,也就意味着线程在 thread cache 申请内存时是不需要加锁的,而一次性申请大于 256KB 内存的情况是很少的,因此大部分情况下申请内存时都是无锁的,这也就是这个高并发内存池高效的地方。
每个线程的 thread cache 会根据自己的情况向 central cache 申请或归还内存,这就避免了出现单个线程的 thread cache 占用太多内存,而其余 thread cache 出现内存吃紧的问题。
多线程的 thread cache 可能会同时找 central cache 申请内存,此时就会涉及线程安全的问题,因此在访问 central cache 时是需要加锁的,但 central 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 没有内存了就会直接去找系统,也就是直接去堆上按页申请内存块。