异步回调程序内存管理
异步回调程序与同步阻塞程序的内存管理方式不同,异步程序是基于回调链引用计数实现内存的管理。本文会用一个最简单的实例讲解异步程序的内存管理。
实例程序
$serv = new Swoole\Http\Server("127.0.0.1", 9502);
$serv->on('Request', function($request, $response) {
$cli = new Swoole\Http\Client('127.0.0.1', 80);
$cli->post('/dump.php', array("key" => 'value'), function ($cli) use ($request, $response) {
$response->end("<h1>{$cli->body}</h1>");
$cli->close();
});
});
$serv->start();
onRequest
- 请求到来这时会触发
onRequest
回调函数,可以得到$request
和$response
对象 - 在
onRequest
回调函数中,创建了一个Http\Client
,并发起一次POST
请求 - 然后
onRequest
函数结束并返回
这时按照正常的PHP
函数调用流程,$request
和$response
对象会被销毁。但在上述程序中,$request
和$response
对象被使用了use
语法,绑定到了匿名函数上,因此这2个对象的引用计数会被加1
。onRequest
函数返回时就不会真正销毁这2个对象了。
引用链依赖
request/response -> post(Closure 回调函数) -> $cli(HttpClient对象) -> post($cli->connect)
$cli
对象,是在onRequest
函数创建的局部变量,按照正常逻辑$cli
对象在onRequest
函数退出时也应该被销毁。但Swoole
底层有一个特殊的逻辑,所有异步客户端对象在发起连接时底层会自动增加一次引用计数,在连接关闭时减少一次引用计数,因此$cli
对象也不会销毁,POST
请求中的匿名函数对象也不会销毁。
Http响应
- 创建的
$cli
对象,接收到来自服务器端的响应,或者连接超时、响应超时,这时会回调指定的匿名函数,调用end
向客户端发送响应 - 回调函数中调用了
$cli->close
这时切断连接,$cli
的引用计数减一。这时匿名函数退出底层会自动销毁$cli
、$request
、$response
3个对象
引用链解除
cli->close -> Closure 销毁 -> $cli 销毁 -> request/response 销毁
多层嵌套
如果Http\Client
的回调函数中调用了其他的异步客户端,如Swoole\Redis
,对象会继续传读引用,形成一个异步调用链。当调用链的最后一个对象销毁时会向着调用链头部逐个递减引用计数,最终销毁对象。
$serv = new Swoole\Http\Server("127.0.0.1", 9502);
$serv->on('Request', function($request, $response) {
$cli = new Swoole\Http\Client('127.0.0.1', 80);
//发起连接,$cli 引用计数增加
$cli->post('/dump.php', array("key" => 'value'), function ($cli) use ($request, $response) {
$redis = new Swoole\Redis;
//发起连接,$redis 引用计数增加
$redis->connect('127.0.0.1', 6379, function ($redis, $result) use ($request, $response, $cli) {
$redis->get('test_key', function ($redis, $result) use ($request, $response, $cli) {
$response->end("<h1>{$result}</h1>");
//关闭连接,$cli 引用计数减少
$cli->close();
//关闭连接,$redis 引用计数减少
$redis->close();
});
});
});
});
$serv->start();
- 这里
$response
和$request
对象被POST
匿名函数、Redis->connect
匿名函数、Redis->get
匿名函数引用,因此需要等到这3个函数执行后,引用计数减少为0
,才会真正的销毁 $cli
和$redis
对象在发起TCP
连接时,会被Swoole
底层增加引用计数。只有$cli->close()
和$redis->close
被调用,或者远端服务器关闭连接,触发$cli->onClose
和$redis->onClose
,$cli
和$redis
这2
个对象的,引用计数才会减少,函数退出时会销毁POST
匿名函数、Redis->connect
匿名函数、Redis->get
匿名函数,3
个对象依附于$cli
和$redis
对象,当$cli
和$redis
对象销毁时,这3
个对象也会被销毁POST
匿名函数、Redis->connect
匿名函数、Redis->get
匿名函数,匿名函数销毁时通过use
语法引用的$response
和$request
对象也会销毁