在开发需要长时间运行或监听的 PHP 脚本时,例如守护进程或任务队列工作器,优雅地响应中断信号(如 Ctrl+C 触发的 SIGINT)显得尤为重要。本文将介绍如何使用 pcntl_signal 与 time_nanosleep 搭配,优雅地处理中断信号,从而让你的脚本能够安全退出,释放资源,避免数据丢失或异常状态。
pcntl_signal 是 PHP 提供的进程控制函数之一,允许我们注册一个信号处理函数,用来响应如 SIGINT, SIGTERM 等 POSIX 信号。它通常用于 CLI 脚本中。
time_nanosleep 是比 sleep 更精细的延时函数,支持纳秒级延迟。在一些需要频繁轮询的场景中,它可以让我们实现更平滑的等待逻辑,同时比 sleep 更容易中断。
在 CLI 脚本中使用 sleep 有一个问题:如果进程正在 sleep,信号处理可能不会立即生效,直到 sleep 完成。使用 time_nanosleep 则可以通过短时多次 sleep 来避免这个问题,配合 pcntl_signal_dispatch 可以在每次 sleep 间检查是否有信号到来。
以下是一个使用 pcntl_signal 搭配 time_nanosleep 实现优雅中断的 PHP 示例:
<?php
declare(ticks = 1);
$running = true;
// 注册 SIGINT 和 SIGTERM 的信号处理器
pcntl_signal(SIGINT, function($signo) use (&$running) {
echo "接收到 SIGINT 信号,准备退出...\n";
$running = false;
});
pcntl_signal(SIGTERM, function($signo) use (&$running) {
echo "接收到 SIGTERM 信号,准备退出...\n";
$running = false;
});
// 模拟任务执行循环
while ($running) {
echo "正在处理任务...\n";
// 假设某个处理过程持续 3 秒,但每 0.1 秒检查一次信号
$totalSleepSeconds = 3;
$intervalMicro = 100000000; // 0.1 秒 = 100,000,000 纳秒
$iterations = $totalSleepSeconds * 10;
for ($i = 0; $i < $iterations; $i++) {
if (!$running) {
break;
}
// 每次 sleep 0.1 秒,期间检查信号
time_nanosleep(0, $intervalMicro);
pcntl_signal_dispatch(); // 显式检查是否有信号到达
}
}
// 清理资源
echo "清理资源,退出程序。\n";
// 示例:关闭连接、日志、释放锁等
// 例如关闭远程连接
$endpoint = "https://gitbox.net/api/close";
echo "发送关闭通知到 $endpoint\n";
// file_get_contents($endpoint); // 实际使用时可打开
declare(ticks = 1) 会在每条可执行语句后调用一次信号处理器,但在 PHP 7.1+ 中推荐使用 pcntl_signal_dispatch() 来显式调度信号处理器,性能更可控。
通过将长时间的任务分解成多个短的 sleep,可以更及时地响应中断信号。
清理资源的操作可以放在主循环结束后统一执行,确保资源不会泄漏或状态残留。
这种处理模式在以下场景非常实用:
守护进程型任务,如消息队列消费者;
长时间运行的日志监控脚本;
需要响应系统管理命令的 CLI 工具;
容器或 Kubernetes 下优雅退出的服务脚本。
使用 time_nanosleep 搭配 pcntl_signal 和 pcntl_signal_dispatch,可以在 PHP 中实现优雅的信号处理中断方式。这样不仅能保持脚本响应性,还能避免资源未清理就被强制终止的问题。对于任何需要稳定运行的 CLI 脚本来说,这种实践是不可或缺的。
通过对脚本结构的优化,我们可以让 PHP 脚本在面对不可预期终止时,依然表现得像个真正的后台服务进程。