PHP的defer延迟加载函数
我们程序退出后需要关闭释放掉资源,如mysql连接,socket连接,文件句柄,锁等等. go有一个延迟加载函数defer. 用来处理函数退出后的善后工作. 那么php常用的善后方法都有哪些呢?
主要功能是函数return之后才处理.
try finally 处理
我们知道finally 在代码异常和正常后都会执行.
- 正常状态
try {
echo "step1".PHP_EOL;
} finally {
echo "finally".PHP_EOL;
}
out:
step1
finally
- error错误状态
try {
echo "step1".PHP_EOL;
trigger_error("error");
} finally {
echo "finally".PHP_EOL;
}
out:
step1
PHP Notice: error in /Volumes/Data/PhpProject/Study/leetcode/defer.php on line 6
Notice: error in /Volumes/Data/PhpProject/Study/leetcode/defer.php on line 6
finally
- exception异常状态
try {
echo "step1".PHP_EOL;
throw new Exception("exception");
} finally {
echo "finally".PHP_EOL;
}
out:
step1
finally
PHP Fatal error: Uncaught Exception: exception in /Volumes/Data/PhpProject/Study/leetcode/defer.php:7
exit退出状态
exit 不能运行.
try {
echo "step1".PHP_EOL;
exit(0);
} finally {
echo "finally".PHP_EOL;
}
对象的__destruct
利用__destruct对象销毁时,执行.也是我们业务中最常用的方法.
1.正常退出
class Db{
public static function run(){
echo "step1".PHP_EOL;
}
public function __destruct(){
echo "destroy".PHP_EOL;
}
}
$db=new Db();
$db->run();
out:
step1
destroy
- Error 错误
Exception 异常
正常执行
exit
exit 也可以运行.
kill
使用kill 或都ctrl+c 退出时,不能执行
register_shutdown 监听exit
- 正常退出
- exit函数退出
- ctrl+c kill 信号退出
信号量退出时,register_shutdown不执行.
pcntl_signal 信号监听
通过注册信号监听,ctrl+c,kill 都能监听.
注意,所有语言都不能处理 kill -9
declare(strict_types=1);
declare(ticks=1);
function sig_handler($signal){
echo "SIG:".$signal.PHP_EOL;
sleep(3);
echo "kill".PHP_EOL;
exit();
};
pcntl_signal(SIGINT, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
//pcntl_signal(SIGKILL, "sig_handler");
echo "step1".PHP_EOL;
sleep(60);
echo "step2".PHP_EOL;
结果:
监听信号量,register_shutdown_function
可以正常监听.
declare(strict_types=1);
// 必须
declare(ticks=1);
// 可以监听
register_shutdown_function(function (){
echo "shutdown".PHP_EOL;
});
function sig_handler($signal){
echo "SIG:".$signal.PHP_EOL;
sleep(1);
echo "kill".PHP_EOL;
};
pcntl_signal(SIGINT, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
echo "step1".PHP_EOL;
sleep(60);
echo "step2".PHP_EOL;
ticks=1
性能有问题,每执行一条代码都要检测信号量.可以使用pcntl_async_signals(true)
提高性能.
declare(strict_types=1);
pcntl_async_signals(true);
register_shutdown_function(function (){
echo "shutdown".PHP_EOL;
});
function sig_handler($signal){
echo "SIG:".$signal.PHP_EOL;
sleep(1);
echo "kill".PHP_EOL;
};
pcntl_signal(SIGINT, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
echo "step1".PHP_EOL;
sleep(60);
echo "step2".PHP_EOL;
自定义defer函数
利用__desctruct 注销时执行.应用或函数关闭会注销变量引用,触发destruct.
function defer(callable $callback){
global $_;
$_=$_??[];
$class=new class(){
public function __destruct()
{
call_user_func($this->callback);
}
};
$class->callback=$callback;
array_push($_,$class);
}
使用:
function main(){
echo "step1".PHP_EOL;
defer(function (){
echo "延迟执行1".PHP_EOL;
});
echo "step2".PHP_EOL;
defer(function (){
echo "延迟执行2".PHP_EOL;
});
return "return 1".PHP_EOL;
}
function foo(){
defer(function (){
echo "延迟执行3".PHP_EOL;
});
return "return 2".PHP_EOL;
}
echo main();
echo foo();
exit();
echo "step3".PHP_EOL;
执行结果:
defer函数有特点:
- 在return 返回之后执行.
- 总是执行,无论函数有异常还是错误.
- 后定义先执行.
- 不用全局作用域,而是把作用域变成当前.
function defer(callable $callback,?array &$context){
$class=new class(){
public function __destruct()
{
call_user_func($this->callback);
}
};
$context??=[];
$class->callback=$callback;
$context[] = $class;
}
测试:
<?php
declare(strict_types=1);
function defer(callable $callback,?array &$context){
$class=new class(){
public function __destruct()
{
call_user_func($this->callback);
}
};
$context??=[];
$class->callback=$callback;
$context[] = $class;
}
function main(){
echo "step1".PHP_EOL;
defer(function (){
echo "延迟执行1".PHP_EOL;
},$context);
echo "step2".PHP_EOL;
defer(function (){
echo "延迟执行2".PHP_EOL;
},$context);
return "return 1".PHP_EOL;
}
function foo(){
defer(function (){
echo "延迟执行3".PHP_EOL;
},$context);
return "return 2".PHP_EOL;
}
echo main();
echo "step3".PHP_EOL;
echo foo();
OUT:
step1
step2
延迟执行1
延迟执行2
return 1
step3
延迟执行3
return 2
异常测试:
function main(){
echo "step1".PHP_EOL;
defer(function (){
echo "延迟执行1".PHP_EOL;
},$context);
echo "step2".PHP_EOL;
defer(function (){
echo "延迟执行2".PHP_EOL;
},$context);
trigger_error("Error");
throw new Exception("Exception");
return "return 1".PHP_EOL;
}
能正常执行.
Exit 退出后也能执行.
function main(){
echo "step1".PHP_EOL;
defer(function (){
echo "延迟执行1".PHP_EOL;
},$context);
echo "step2".PHP_EOL;
defer(function (){
echo "延迟执行2".PHP_EOL;
},$context);
exit(1);
}
main();
// 输出
step1
step2
延迟执行1
延迟执行2
后进先出
PHP自带SplStack结构,后进先出
$stack= new SplStack();
$stack[]=1;
$stack[]=2;
$stack[]=3;
foreach ($stack as $item) {
echo $item."\n";
}
// out:
3
2
1
defer函数改成:
同一个$content,会为引用销毁.
function defer(callable $callback,?SplStack &$context){
$context??=new class() extends SplStack {
public function __destruct()
{
while(!$this->isEmpty()) {
\call_user_func($this->pop());
}
}
};
$context->push($callback);
}
完整测试代码:
function defer(callable $callback,?SplStack &$context){
$context??=new class() extends SplStack {
public function __destruct()
{
while(!$this->isEmpty()) {
\call_user_func($this->pop());
}
}
};
$context->push($callback);
}
function main(){
echo "step1".PHP_EOL;
defer(function (){
echo "延迟1".PHP_EOL;
},$_);
echo "step2".PHP_EOL;
defer(function (){
echo "延迟2".PHP_EOL;
},$_);
echo "step3".PHP_EOL;
defer(fn()=>print_r("延迟3".PHP_EOL),$_);
return 0;
}
// 箭头函数
defer(fn()=>print_r("延迟A".PHP_EOL),$_);
defer(fn()=>print_r("延迟B".PHP_EOL),$_);
main();
echo "step4".PHP_EOL;
try{
defer(fn()=>print_r("异常延迟A".PHP_EOL),$_);
throw new Exception("抛出异常.");
}catch(Exception $e){
defer(fn()=>print_r("异常延迟B".PHP_EOL),$_)
}
Out:
step1
step2
step3
延迟3
延迟2
延迟1
step4
异常延迟B
异常延迟A
延迟B
延迟A
ps: 箭头函数
php7.4之后才支持的,不能用复杂的运算,或大括号.
array_map(fn($it)=>$it+1,[1,2,3]);
// out: [2,3,4]
socket 的defer例子
之前:
// 创建服务端
$socket=stream_socket_server("0.0.0.0:8000",
$errCode);
// 创建客户端
$client=stream_socket_accept($socket);
// 其他操作....
echo "read:",fread($client,1024);
fwrite($client,"ok");
// 关闭客户端
fclose($client);
// 关闭服务端
fclose($socket);
exit();
现在:
创建连接和关闭连接成对写在一起, 这样写就很直观了,不用担心忘记关闭了.
// 延迟函数
function defer(callable $callback,?SplStack &$context){
$context??=new class() extends SplStack {
public function __destruct()
{
while(!$this->isEmpty()) {
\call_user_func($this->pop());
}
}
};
$context->push($callback);
}
// 创建服务端
$socket=stream_socket_server("0.0.0.0:8000",
$errCode);
// 关闭服务端
defer(fn()=>fclose($socket),$_);
// 创建客户端
$client=stream_socket_accept($socket);
// 关闭客户端
defer(fn()=>fclose($client),$_);
// 其他操作
echo "read:",fread($client,1024);
fwrite($client,"ok");
exit();
退出后自动关闭连接.
原作者:阿金
本文地址:https://hi-arkin.com/archives/php-defer.html