我们程序退出后需要关闭释放掉资源,如mysql连接,socket连接,文件句柄,锁等等. go有一个延迟加载函数defer. 用来处理函数退出后的善后工作. 那么php常用的善后方法都有哪些呢?

主要功能是函数return之后才处理.

try finally 处理

我们知道finally 在代码异常和正常后都会执行.

  1. 正常状态
try {
    echo "step1".PHP_EOL;
} finally {
    echo "finally".PHP_EOL;
}

out:

step1
finally
  1. 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
  1. 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
  1. 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
  1. Error 错误

image-20230919150824881

  1. Exception 异常

    正常执行

image-20230919150918067

  1. exit

    exit 也可以运行.

image-20230919151115694

  1. kill

    使用kill 或都ctrl+c 退出时,不能执行

    image-20230919151317539

    image-20230919151645121

register_shutdown 监听exit

  1. 正常退出

image-20230919152214992

  1. exit函数退出

image-20230919152032290

  1. ctrl+c kill 信号退出

信号量退出时,register_shutdown不执行.

image-20230919152509826

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;

结果:

image-20230919153028890

监听信号量,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;

执行结果:

image-20230919162141506

defer函数有特点:

  1. 在return 返回之后执行.
  2. 总是执行,无论函数有异常还是错误.
  3. 后定义先执行.
  1. 不用全局作用域,而是把作用域变成当前.
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;
}

能正常执行.

image-20230919173512488

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();

退出后自动关闭连接.

image-20230919214109637

原作者:阿金
本文地址:https://hi-arkin.com/archives/php-defer.html

标签: php defer 延迟函数

(本篇完)

评论