Apache内存池(apr_pool_t)

APR Pool

大多数libapr的API都依赖于内存池,借助内存池,简化了内存块的管理。想像一下没有内存池系统的情况:你申请一些内存块就必需逐个释放它们,例如如果你申请了10个内存块,你必需释放10次,否则,你将遭受内存泄露的错误。内存池解决了这个令人感到繁琐的问题,在申请一个内存池之后,你可以从内存池中申请多个内存块,释放它们的时候,你所需要做的就是销毁内存池,这样你就可以释放所有的内存块了。这有两个优点,第一,它可以预防内存泄露的错误;第二,分配内存块的开销相对变低了。从某种意义上说,内存池迫使你遵循面向会话编程,一个内存池就是一种会话内容,这样,处于同一个内存池中的对象就有相同的生命周期,你可以通过控制会话内容来控制对象。在一个会话的开始,你创建了一个内存池,接着,你在内存池中创建了一些对象,你不需要去关心这些对象的生命周期,最后,在会话结束的时候,你只需要将那个内存池销毁就可以了。

注:通常,对象生命周期控制是程序开发最困难的部分,因此,针对这个问题还存在有一些技术,例如智能指针,垃圾回收机制等等。需要注意,同时使用这些技术有一定的难度,内存池也是这其中的一项技术,所以你不得不非常小心的使用它们。

注:在将来,libapr的内存池将变得不再那么重要。参见http://mail-archives.apache.org/mod_mbox/apr -dev/200502.mbox/%3c1f1d9820502241330123f955f@mail.gmail.com%3e

下面有三个基本的API函数:

/* 摘自 apr_pools.h */
APR_DECLARE(apr_status_t) apr_pool_create(
    apr_pool_t **newpool, apr_pool_t *parent);
APR_DECLARE(void *) apr_palloc(apr_pool_t *p, apr_size_t size);
APR_DECLARE(void) apr_pool_destroy(apr_pool_t *p);

我们使用apr_pool_create()函数创建一个内存池,这个内存池将一直存活,直到你调用apr_pool_destroy()函数以后被销毁。apr_pool_create()的第一个参数是一个结果输出参数,是一个新创建的apr_pool_t类型的内存池对象。通过调用 apr_palloc()来申请指定大小的内存块,具体使用方法见mp-sample.c

/* 摘自 mp-sample.c */
    apr_pool_t *mp;
    /* 创建内存池 */
    apr_pool_create(&mp, NULL);
 
    /* 从内存池中分配内存块 */
    char *buf1;
    buf1 = apr_palloc(mp, MEM_ALLOC_SIZE);

简单地说,我们可以像使用malloc(3)这样使用apr_palloc(),也可以调用apr_pcalloc(),正如你猜到的, apr_pcalloc类似于calloc(3),apr_pcalloc返回一个被0填充了的内存块。假如你使用了malloc(3)/calloc (3),你需要调用free(3)来释放分配了的内存。但是在内存池中,你必不需要释放每个内存块,你只需要对该内存池调用 apr_poll_destroy()函数从而释放所有的内存块。

注:使用apr_palloc()申请内存,其内存块的大小没有限制,然而,在内存池中申请大内存并不是什么好主意。内存池本质上是为了更小的内存块而设计的,实际上,初始的内存池的大小是8000字节。如果你需要申请超过几兆字节的内存块时,那么就不要使用内存池。
注:默认情况下,内存池管理器从不将申请到的内存归还给系统。如果程序要运行很长时间,这将是一个问题,推荐像下面的代码那样指定一个上限:

/* 设置上限,让内存池管理器释放内存,将内存返回给系统的示例代码 */

#define YOUR_POOL_MAX_FREE_SIZE 32      /* apr_pool max free list size */
apr_pool_t *mp;
apr_pool_create(&mp, NULL);
apr_allocator_t* pa = apr_pool_allocator_get(mp);
if (pa) {
    apr_allocator_max_free_set(pa, YOUR_POOL_MAX_FREE_SIZE);
}

这儿有两个API函数需要知道,一个是apr_pool_clear(),另一个是apr_pool_cleanup_register(),apr_pool_clear()类似于apr_pool_destroy(),不同的是内存池将一直存在。示例代码如下:

/* 使用apr_pool_clear()的例子 */
apr_pool_t *mp;
apr_pool_create(&mp, NULL);
for (i = 0; i < n; ++i) {
    do_operation(..., mp);
    apr_pool_clear(mp);
}
apr_pool_destroy(mp);

do_operation()里使用了内存池,分配了一些内存块。假如在do_operation()之外不需要这些内存块了,可以调用 apr_pool_clear()函数,这样能缩小内存的使用大小。如果你熟悉系统的栈内存的话,你会觉得内存池与栈内存一样,调用apr_palloc 只是如同移动SP(栈指针),调用apr_pool_clear()如同重置SP,两者都是轻量级的操作。

使用apr_pool_cleanup_register()函数,可以在内存池清空/销毁上设定一个钩子(回调)函数,在内存池清空或是销毁后调用这个函数,在这个回调函数中,你可以实现任何在内存池上的结束代码。

关于内存池的最后一个主题是子池,每个内存池都可以有一个父内存池,因此,内存池构造了树。apr_pool_create()的第二个参数表示父内存池,当这个参数为NULL时,新创建的内存池将变为一个根内存池,可以在这个根内存池上创建子内存池。在这个树中对一个内存池调用 apr_pool_destroy()函数,则该内存池的子内存池也将被销毁;当对该内存池调用apr_pool_clear()函数,则这个内存池存在但是它的子内存池将被销毁,上面提及到的那些清除函数,在子内存池销毁时被调用。

注:当将NULL值做为清除回调函数时将会产生一个bug,你必须像下面的代码那样传入apr_pool_cleanup_null:

/* 关于内存池典型bug的伪代码 */
/* apr_pool_cleanup_register(
    mp, ANY_CONTEXT_OF_YOUR_CODE, ANY_CALLBACK_OF_YOUR_CODE, NULL);
    这将产生一个bug */
 
/* 修正: */
apr_pool_cleanup_register(
    mp, ANY_CONTEXT_OF_YOUR_CODE,
    ANY_CALLBACK_OF_YOUR_CODE,
    apr_pool_cleanup_null);

出处

这个指南主要介绍如何使用libapr(apache portable runtime)。
版权所有,Copyright (C) 2005 INOUE Seiichiro <inoue&ariel-networks.com>,翻译:成彦
原文地址:http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-3.html
译文出处:http://hi.baidu.com/yeetoo/blog/item/f6aced24a67ac1308744f98a.html
转载时请保留版权信息。

相关参考

Add a New Comment
or Sign in as Wikidot user
(will not be published)
- +
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License

Subscription expired — please renew

Pro account upgrade has expired for this site and the site is now locked. If you are the master administrator for this site, please renew your subscription or delete your outstanding sites or stored files, so that your account fits in the free plan.