Chinaunix首页 | 论坛 | 博客
  • 博客访问: 17060
  • 博文数量: 11
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 50
  • 用 户 组: 普通用户
  • 注册时间: 2015-04-03 11:30
文章分类

全部博文(11)

文章存档

2015年(11)

我的朋友

分类: C/C++

2015-04-10 14:00:06

3.4.2  APR池

APR池为资源管理提供了一个可选的模型。和垃圾回收类似,APR池将程序员从各种可能的情况下进行清理操作的复杂性中解放出来。除此之外,APR池具有一些其他的优势,包括对资源的生命周期内的完全控制和管理不同种类资源的能力。

APR池的基本理念和此类似:无论何时你分配了一个需要清理的资源,你必须使用一个池来对其进行注册。然后这个池就负责对这个资源进行清理,清理操作将会在这个池本身被清理的时候发生。使用这种方式,资源管理的问题被简化成为对一个单一资源的分配和清除:池本身。如果Apache的池是被服务器来管理,那么就从应用编程的角度不去涉及资源管理的复杂性。程序要做的事情就是为一个资源的生命周期选择一个合适的池。

基本的内存管理

池最基本的应用就是内存管理。我们不使用如下的方式:

mytype* myvar = malloc(sizeof(mytype)) ;
/* 在任一个可能的执行路径上,该资源需要被释放 */

而是采用如下的方式:

mytype* myvar = apr_palloc(pool, sizeof(mytype)) ;

不管在此期间发生了什么,池将自动地负责释放这个资源。第二个好处就是池的分配在很多平台上都比molloc快得多。

基本的内存管理在APR和Apache中已经成形,内存使用其他的函数进行分配。下面是字符串操作和审计的例子,我们可以从中马上获益,使用APR版本的sprintf()不需要事先知道一个字符串的大小:

char* result = apr_psprintf(pool, fmt, ...) ;

APR也提供了池内存的高层次抽象——例如,用来向过滤链传送数据的Bucket。

普遍适用的内存管理

APR提供了用来进行内存管理内建的函数,以及一些其他的基本资源,例如文件、套接字和互斥信号量。但是,程序员不需要使用这些函数和资源。一个可选方案就是使用本地的分配函数并通过池显式地注册一个清理操作。

mytype* myvar = malloc(sizeof(mytype)) ;
apr_pool_cleanup_register(pool, myvar, free,
apr_pool_cleanup_null);

或者

FILE* f = fopen(filename, "r") ;
apr_pool_cleanup_register(pool, f, fclose, apr_pool_cleanup_null);

这个代码将清理的责任委托给池,因此对于程序员来说则不需要额外的步骤。不过,相对于APR的池,本地的、功能和APR的apr_pools和apr_file_io类似的函数,其移植性要差一些,而且molloc在绝大部分平台上比使用池要慢一些。

这种对任何资源普遍适用的内存管理方法对于Apache和APR是透明的。例如,如果想打开一个MySQL数据库连接并在使用之后关闭,你可以写下面的代码。

MYSQL* sql = NULL ;
sql = mysql_init(sql) ;
if ( sql == NULL ) { log error and return failure ; }
apr_pool_cleanup_register(pool, sql, mysql_close,
apr_pool_cleanup_null) ;

sql = mysql_real_connect(sql, host, user, pass,
dbname, port, sock, 0) ;
if ( sql == NULL ) { log error and return failure ; }

注意,apr_dbd(我们将在第11章进行讨论)为管理数据库连接提供了一个总的来说更好的方法。

作为第二个例子,考虑XML的处理。

xmlDocPtr doc = xmlReadFile(filename);
apr_pool_cleanup_register(pool, doc, xmlFreeDoc,
apr_pool_cleanup_null) ;

/* 我们目前在操作doc,将需要分配内存,
* 该内存由XML库管理,不过将通过xmlFreeDoc 释放。
*/

如果集成C++的析构函数做资源释放,我们提供了下面这个例子,假设我们定义了一个类:

class myclass {
public:
virtual ~myclass() { do cleanup ; }
// ....
} ;

我们定义了一个C的封装:

void myclassCleanup(void* ptr) { delete (myclass*)ptr ; }

然后我们在分配myclass时通过池注册这个封装:

myclass* myobj = new myclass(...) ;
apr_pool_cleanup_register(pool, (void*)myobj, myclassCleanup,
apr_pool_cleanup_null) ;

//我们已经为C++的资源管理在Apache中挂了钩子
//我们不再需要删除myobj
//池将为我们做这个清除操作

隐式和显式地清除

假设我们想要在请求结束之前显式地释放资源——例如,因为我们正在做一些内存消耗比较大的操作而有些对象我们可以释放。我们可能会想根据普通的代码域规则做一些事情,或者仅仅使用基于池的清理作为后退(fallback)来处理错误路径。不过,由于我们已经注册了清理,因此清理操作将不顾我们的意愿而运行。在最坏的情形下,它可能会导致一个两次释放(double-free)并产生段错误。

另外一个池函数,apr_pool_cleanup_kill,就是用来处理这种情形的。当我们运行了显式的清除,我们从池中注销清理操作。当然,我们对于如何进行这项任务有更加聪明的选择。下面是一段代码摘要,一个C++类基于一个池管理自己,不管是否显式删除了这个池。

class poolclass {
private:
apr_pool_t* pool ;
public:
poolclass(apr_pool_t* p) : pool(p) {
apr_pool_cleanup_register(pool, (void*)this,
myclassCleanup, apr_pool_cleanup_null) ;
}
virtual ~poolclass() {
apr_pool_cleanup_kill(pool, (void*)this, myclassCleanup) ;
}
} ;

如果你使用C++开发Apache(或者APR),你可以从poolclass类继承。大部分的APR函数做的和这个一样,随时在资源被分配或者清除时进行注册和清理操作。

在C语言中,我们可以使用以下的通用格式。

/* 分配一些资源 */
my_type* my_res = my_res_alloc(args) ;
/* 处理错误 */
if (my_res == NULL) {
/* 记录错误并跳出 */
}
/* 通过注册清理保证资源不会泄漏 */
apr_pool_cleanup_register(pool, my_res,
my_res_free, apr_pool_cleanup_null) ;

/* ... 现在就按照需求使用这个资源 ... */

/* OK,我们已经完成任务,现在要尽快释放资源了 */
rv = my_res_free(my_res) ;
/* 既然我们已经释放了,我们必须kill掉清除操作 */
apr_pool_cleanup_kill(pool, my_res, my_res_free) ;
/* 现在进行错误处理并继续 */
if (rv != APR_SUCCESS) { /* or whatever test may be appropriate */
/* ... 记录错误并跳出,或者尝试进行恢复 ... */
}

我们甚至可以将这个格式进行流水线作业,通过使用一个函数在池中进行清理和注销。

apr_pool_cleanup_run(pool, my_res, my_res_free) ;

阅读(725) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~