请手动释放你的资源(Pleaserelease resources manually)

来源:互联网 发布:php教程下载 编辑:程序博客网 时间:2024/06/09 15:17


·        作者Laruence(   )

·        本文地址http://www.laruence.com/2012/07/25/2662.html

·        转载请注明出处

我从来不认为这个问题是个问题,直到昨天.

昨天晚上的时候,我提交了一个RFC,关于引入finallyPHP,实现这个功能的出发点很简单,因为我看见不少人的需求,另外还有就是Stas,一直只看到讨论, 没看到有人实现. 于是我就给实现了.

发到邮件组以后,一个开发组的同学NikitaPopov(nikic),表示强烈反对这个RFC,当然最初的论点他说了很多,最后我们在线讨论的时候,他表达了一个他的观点:

“PHP在请求结束后会释放所有的资源,所以我们没有必要调用fclose,或者mysql_close来释放资源, PHP会替我们做

并且他表示,他从来都不会调用fclose,认为fclose的存在只是为了继承C函数族.

我很惊讶,我也不知道还有多少人是和他一样的想法,所以我决定写这篇文章.

PHP5.2以前, PHP使用引用计数(Reference count)来做资源管理,当一个zval的引用计数为0的时候,它就会被释放. 虽然存在循环引用(Cycle reference),但这样的设计对于开发Web脚本来说,没什么问题, 因为Web脚本的特点和它追求的目标就是执行时间短,不会长期运行. 对于循环引用造成的资源泄露, 会在请求结束时释放掉. 也就是说, 请求结束时释放资源,是一种部补救措施(backup).

然而,随着PHP被越来越多的人使用,就有很多人在一些后台脚本使用PHP,这些脚本的特点是长期运行,如果存在循环引用, 导致引用计数无法及时释放不用的资源,则这个脚本最终会内存耗尽退出.

所以在PHP5.3以后,我们引入了GC, 也就是说, 我们引入GC是为了解决用户无法解决的问题.

这个是历史,我简单介绍下, 现在让我们回头来看开头的问题, 是不是因为PHP会在请求结束后释放所有的资源,于是我们就可以不用手动释放呢?

看一个例子:

Mysql最大连接数(mysql.max_connections)

1.        <?php

2.           $db =mysql_connect() ;

3.           $resut =mysql_query();

4.           // process result...

5.           usleep(500);

6.        

7.           //mysql_close($db);  let's say, you didn't call to this

8.        

9.           // other logic, assuming it costs 5s

10.        sleep(5);

11.     

12.        exit(0);//finish

上面的例子,我们会保持一个和Mysql的连接5秒钟,这样的脚本对于一般的应用来说没有关系,但是对于一个请求量很大的脚本来说,会导致一个致命问题:

比如一个繁忙的应用,每秒要处理来自用户的1000个请求,那么5秒钟请求多少个? 5 * 1000 = 5000,Mysql有最大连接数限制(mysql.max_connections),这个数字一般不超过2000,默认的会更低:(mysql.max_connections),

那么,这样代码会导致你的应用,根本无法正常提供服务.而如果我们在对Mysql的处理完成后就关闭这个连接,那么就不会触发这个问题.

而我们在实践中,遇到过一个更加实际的问题,看下面的例子:

1.       <?PHP

2.       $mmc =newMemcached();

3.       $mysql =mysql_connect();

4.       //process

5.       mysql_close($mysql);

6.       $mmc->close();

这是一个真实的教训,代码如上面所示, 突然有一天我们的Mysql出现了问题,导致连接Mysql的耗时增大,然后就导致, 一个脚本对Memcached连接占用过长,最后Memcache因为连接数太多,就拒绝服务了..

所以,我们一定要让连接代价最高的资源,最先初始化.

系统最大句柄(/proc/sys/fs/file-max)

这个很简单,如果你持续打开句柄, 而不释放, 那么你有可能触发系统最大句柄限制,对于进程来说, 自己还有进程可打开句柄数限制(ulimit -n).

系统调用是昂贵的(System call isexpensive)

PHP之所以会在请求结束后正确的释放掉所有的资源,内存, 这是因为当我们在脚本中使用新的内存的时候, PHP会向OS申请一大块内存(ZEND_MM_SEG_SIZE大小),然后分给你你需要的合适的一块小内存.

当你不使用这块小内存的时候, PHP也不会返还给OS,而是保留下来给后续的处理使用.

我们知道, malloc(3)会导致系统调用(brk(2))(当然也可能是mmap,我们此处不考虑这个细节, thanks to华裔), 而系统调用是昂贵的.

所以,如果你使用完了资源不及时释放,那么后续的逻辑如果请求内存, PHP发现之前申请的一大块内存已经分光了,它就只好再次向OS发起malloc调用,得到一块新的大内存. 并且它还需要对这个大内存做一些标记处理..

而如果你使用完资源,及时释放的话, 那么下次脚本申请内存的时候, 你之前归还的内存块就可以被重复利用,那么也许你的整个脚本只需要和OS申请一次内存.

内存峰值(Memory peak usage)

这个和上面的有一定的关系,当你使用完资源就释放,然后后续又使用这样的资源.那么PHP的内存占用会是:

资源+1 ->资源-1 -> 资源+1 -> 资源-1 (峰值是1)

而如果你是等到PHP请求结束再释放:

资源+1 ->资源 + 1 …. ->资源 -1 -> 资源 – 1 (峰值是2)

也就说,一个良好的编写的脚本可能要比一个瞎写的脚本,要省很多峰值内存..

考虑一个极端情况,对一个很繁忙的服务器来说,比如有10PHP进程,每个PHP进程最大1G内存,而服务器只有8G内存.

结论 (conclusion)

结论很明显,我开头也说过了, 我从来不认为这个是个问题.

这里说一句,如果你买了一本PHP的书,它告诉你: “不用在PHP主动释放资源,因为PHP会帮你释放的话,我建议你, 烧了它.

 

0 0