如果您以 CLI 方式运行 PHP 并作为“守护进程”(即循环中),则必须在每个循环中调用此函数以检查是否有新的信号等待分发。
(PHP 5 >= 5.3.0, PHP 7, PHP 8)
pcntl_signal_dispatch — 调用待处理信号的信号处理程序
此函数没有参数。
示例 #1 pcntl_signal_dispatch() 例子
<?php
echo "安装信号处理程序...\n";
pcntl_signal(SIGHUP, function($signo) {
echo "信号处理程序被调用\n";
});
echo "生成 SIGHUP 信号到自身...\n";
posix_kill(posix_getpid(), SIGHUP);
echo "分发...\n";
pcntl_signal_dispatch();
echo "完成\n";
?>
以上示例将输出类似于以下内容
Installing signal handler... Generating signal SIGHUP to self... Dispatching... signal handler called Done
请注意,从由之前的 pcntl_signal_dispatch() 调用的信号处理程序中调用 pcntl_signal_dispatch() 将不会触发任何新的待处理信号的处理程序。这意味着,如果您编写一个响应信号分叉子进程的 CLI 守护进程,那么这些子进程将无法响应信号。这让我头疼了一段时间,因为当发生这种情况时,pcntl_signal_dispatch() 不会引发任何错误。一个解决方案是在信号处理程序中设置一个标志,并在父进程的主循环中的其他地方对其做出反应(通过分叉所需的子进程)。
正如“me at subsonic dot net”所指出的那样,从由之前的 pcntl_signal_dispatch() 调用的信号处理程序中调用 pcntl_signal_dispatch() 将不会触发任何新的待处理信号的处理程序。即使您 pcntl_exec() 一个新的 PHP 处理器来执行一个完全不同的脚本,这似乎也是正确的。
解决方案似乎是在 ticks_handler() 内显式调用 pcntl_signal_dispatch()。并将 sig_handler(int) 用作推送到队列的函数。在 ticks_handler 中调用分发之后,立即弹出您的队列,执行您本来会在信号处理程序中执行的操作,直到队列为空。
好吧,我之前说错了。仅仅在信号处理程序之外处理信号是不够的。它们必须在 tick 处理程序(显式或隐式)之外处理。所以……
注册一个调用 pcntl_signal_dispatch() 的 tick 处理程序;
在信号处理程序中,将您的信号入队;
在脚本的主循环中,处理您的信号;
<?php
declare(ticks=1);
global $sig_queue;
global $use_queue;
$sig_queue = array();
$use_queue = true; // 设置为 false 以使用旧方法
function tick_handler()
{
pcntl_signal_dispatch();
}
function sig_handler($sig)
{
global $sig_queue;
global $use_queue;
if(isset($use_queue) && $use_queue)
{
$sig_queue[] = $sig;
}
else
{
sig_helper($sig);
}
}
function sig_helper($sig)
{
switch($sig)
{
case SIGHUP:
$pid = pcntl_fork();
if($pid) print("forked $pid\n");
break;
default:
print("未处理的信号: $sig\n");
}
}
pcntl_signal(SIGHUP, "sig_handler");
while(true)
{
if($use_queue) foreach($sig_queue as $idx=>$sig)
{
sig_helper($sig);
unset($sig_queue[$idx]);
}
sleep(1);
}
?>