用AI赚第一桶💰低成本搭建一套AI赚钱工具,源码可二开。 广告
# 异步回调程序内存管理 [TOC] 异步回调程序与同步阻塞程序的内存管理方式不同,异步程序是基于回调链引用计数实现内存的管理。本文会用一个最简单的实例讲解异步程序的内存管理。 ## 实例程序 ~~~ $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`对象也会销毁