vlambda博客
学习文章列表

揭秘池化技术--内存池的实现


池化技术


在系统开发过程中,我们经常会用到池化技术来减少系统消耗,提升系统性能。池化技术主要有线程池、内存池、连接池、对象池等等。线程、内存、数据库的连接对象都是资源,在程序中,当你创建一个线程或者在堆上申请一块内存的时候都涉及到很多的系统调用,也是非常消耗CPU的。如果你的程序需要很多类似的工作线程或者需要频繁地申请释放小块内存,在没有对这方面进行优化的情况下,这部分代码很可能会成为影响你整个程序性能的瓶颈。我们今天主要讲一讲内存池。


malloc/free的缺陷


1- 内存未分配成功,我们就错误的使用了它。
2- 内存分配虽然成功,但是尚未初始化就引用它。
3-  内存分配成功并且已经初始化,但操作越过了内存的边界。
4- 忘记了内存释放,造成内存泄露。


我相信这些缺陷,大部分同学在学习C语言的过程中都会遇到过,确实也是很难避免。 关键是这样使用内存它效率低啊。 比如在C/S服务器中,需要频繁地收发数据包。而数据包的内存采用原始的malloc-free模式,会大大降低服务器性能,所以想到了使用内存池技术。


首先是创建内存池。 这个过程的主要任务是预先分配足够大的内存,形成一个初步的“内存池”。 分配内存,也就是用户请求内存时,会返回内存池中一块空闲的内存,并将其标志置为已使用,当然具体细节和方法有很多。 释放内存时,不是真正地调用free或是delete的过程,而是把内存放回内存池的过程。 在把内存放入内存池的同时,要把标志位置为空闲。 最后在应用程序结束时,要把内存池销毁。 这里主要做的工作就是把内存池中的每一块内存释放。


设计思路


一、内存存放结构


揭秘池化技术--内存池的实现


二、第一次“借用”


遍历内存池,发现内存块1空闲(可借)。标记内存块1为“已借”,记录此时位置(1,1)。然后把内存块1“借出去”。


揭秘池化技术--内存池的实现


三、连续几次“借出”,中途可能有几次“归还”


“借”的时候都是在上次“借出”的位置基础上往后遍历,运气差会遍历到末尾。当然,多线程嘛,中途可能有几次归还,对借出也没干扰。归还时,标记块的状态为“空闲”即可。状态为bool值,在一读一写的情况下,不需要加锁。但是如果是多个线程同时借的话,就需要考虑同步的问题了,即加锁。


揭秘池化技术--内存池的实现


四、遍历到末尾了


在借借还还中,总会出现这种情况。当前位置(3,1),后边的块全部标记为“已借”。很明显,这样会直接遍历到末尾。后边没数据了,只有看看头部到(3,1)这部分数据块有空闲的没。很幸运的是,遍历到了(2,2)空闲。


揭秘池化技术--内存池的实现


五、没有空闲的块了


当从当前位置往后遍历,没有空闲块。然后在从头部遍历到当前位置,依然没有空闲块。说明,该添加新的内存到内存池了。然后,直接返回刚申请内存的第一块。



这只是众多思路中的一个。麻烦的就是需要考虑多线程,有多线程就得加锁,加锁就是变相的单线程,就会影响效率。当然,也只有借内存需要加锁,还内存可以不加锁。


总结


最后,我们再来总结一下使用内存池的 好处

1、减少了内存碎片的产生。这个可以从创建内存池的过程中看出。我们在创建内存池时,分配的都是一块块比较整的内存块,这样可以减少内存碎片的生。

2、提高了内存的使用效率。这个可以从分配内存和释放内存的过程中看出。每次的分配与释放并不是去调用系统提供的函数或是操作符去操作实际的内存,而是在复用内存池中的内存。

当然,没有完美的技术存在,内存池也存在缺点


就是很有可能会造成内存的浪费,原因也很明显,开始分配了一大块内存,不是全部都用得到的。所以大家也要根据自己的项目需求,判断是不是需要内存池,如果需要,内存池应该设置为多大比较合理。