APR 是 Apache Portable Runtime project 的简称,对于 APR 是否值得作为日常开发工具箱里面的一员,我不做太多的评价,对于 APR 1.x 过分依赖 memory pool 的讨论也很多,各位自行去实践。
对于 APR,我并没有太多关注它的 portable,日常用的较多的是它的 memory pool 实现。memory pool 可以帮助我们在有限的开销下简化对内存块申请释放的管理,让我们的代码更优雅,更易维护。另外对于 24 X 7 长时间不间断运行的服务器程序来说,海量 malloc/free 造成内存碎片也是通常大家需要面对的问题。当然这个改进的办法有很多,我自己实践过的有下面几种,实现自己的内存申请释放管理机制(内存池),重用 struct 或者 object,对每次 malloc 的内存块大小对齐等等。就目前而言,我觉得 apr memory pool + struct/object 重用已经能满足上述两个需求。下面说说我对 apr memory pool 的一点见解。
1, apr memory pool 大多操作都是非线程安全,任何时候只能有一个线程对同一个 pool 进行操作,除了一个例外(APR 1.4.5)。
* Note that most operations on pools are not thread-safe: a single pool
* should only be accessed by a single thread at any given time. The one
* exception to this rule is creating a subpool of a given pool: one or more
* threads can safely create subpools at the same time that another thread
* accesses the parent pool.
*/
针对这点,很直接的思考就是用 “锁” 去保护。我个人的倾向或者说喜好是 lock free。过多的“锁”必然降低目前多核机器下程序的并发度,也对程序并发架构的优化带来其他问题。针对这点,我在使用 apr memory pool 的时候,通常不引入另外的“锁”,而是和 data context 关联在一起,让 data 的“锁”去间接保护。
typedef struct
{
apr_pool_t *pool;
MyMutex *data_mutex;
void *data;
} data_context_t;
2, 我会适当使用 subpool,这个也得益于 apr memory pool 在创建 subpool 的时候是线程安全的。
在同一个 app context 下,会创建一个属于该 app context 的 memory pool,而从属于该 app context 的 data 的 memory pool 就会是它的 subpool。另外在对临时内存用量要求较多的函数或者循环代码都会生成 subpool,在使用完毕后释放。
void func_require_large_tmp_mem(apr_pool_t *parent_pool, ...)
{
apr_pool_t *subpool;
apr_pool_create(&subpool, parent_pool);
...
apr_pool_destroy(subpool);
}
apr_pool_t *subpool;
apr_pool_create(&subpool, parent_pool);
while (cond)
{
.... // large tmp mem require
}
apr_pool_destroy(subpool);
3, memory pool 不是万能的,某些条件下使用 object pool 也是不错的选择
memory pool 在内存不足的情况下需要从 OS 额外获取。这个通常发生在该 memory pool 初始内存大小或者剩余可用内存大小小于外部需求的时候。如果一个 app,不同 object 使用的 mem 差异是较大的,那么会造成要么 memory pool 浪费太多(APR MIN_ALLOC 是 8192),或者 memory pool 需要频繁的扩大。要解决这个问题,一个方式是在创建 memory pool 的时候根据程序上下文以及所需要处理的数据指定 memory pool 预先准备的可用内存大小以及后续扩大可用内存的大小。在这个时候,object
pool 也许就应该作为我们的一个考虑。每个类型的 object 因为处理同一类的数据或者消息,因此重用该 object,可以让 memory pool 释放空余内存或者申请额外可用内存的操作大大降低。同时也能一定程度上避开 malloc/free 等内存管理在多核系统里面的并发瓶颈(当然我们可以 PRELOAD 外部的 malloc/free 实现去替换原有的)。