PHP 5.6 依赖 declare(ticks = 1) 轮询处理信号,否则信号不触发;PHP 7.0 改为主动调用 pcntl_signal_dispatch(),更可控且可禁用 ticks;两者均无法捕获 SIGKILL,子进程需显式回收防僵尸。

PHP 7.0 的 pcntl 多进程信号处理默认更可靠,但必须显式调用 pcntl_signal_dispatch();PHP 5.6 则严重依赖 declare(ticks = 1),否则信号几乎不触发——这是最常踩的坑。
PHP 5.6 中 signal 不生效,大概率是因为没写 declare(ticks = 1)
PHP 5.6 的 pcntl_signal() 完全依赖 ticks 机制轮询检查信号队列。不加 declare(ticks = 1),信号注册了也白注册,进程收不到任何回调。
- 必须放在脚本最顶部(或至少在
pcntl_signal()调用前),且作用域覆盖信号可能到达的执行路径 -
ticks = 1表示每执行一条低级语句就检查一次信号,开销大但“能用”;设为更大值(如 10)会明显降低响应及时性 - 若代码中有长时间阻塞操作(如
sleep(30)、fgets(STDIN)),ticks 在阻塞期间不触发,信号会延迟甚至丢失 - Web SAPI 下完全不可用,仅限 CLI
PHP 7.0+ 可以禁用 ticks,改用主动 dispatch
PHP 7.0 引入了更可控的信号分发模型:pcntl_signal() 注册后,信号进入队列,但不自动执行;你得在循环中手动调用 pcntl_signal_dispatch() 触发回调。
- 去掉
declare(ticks = 1)可显著降低 CPU 开销,尤其在空闲循环中 - 推荐结构:
while ($running) { sleep(1); pcntl_signal_dispatch(); } - 注意:如果循环里有长耗时操作(如数据库查询、curl 请求),
pcntl_signal_dispatch()被卡住不执行,信号仍会延迟 —— 必须确保 dispatch 调用频次足够高 - PHP 7.4+ 还支持
pcntl_async_signals(true)启用异步信号(绕过 ticks 和 dispatch),但需谨慎:它可能在任意 opcode 中断执行,导致资源竞争或内存不一致
两个版本都忽略的 SIGKILL 和信号掩码行为
SIGKILL(kill -9)在 PHP 5.6 和 7.0 中均无法捕获或屏蔽,这是内核强制行为,不是 PHP 实现问题。
立即学习“PHP免费学习笔记(深入)”;
-
pcntl_sigprocmask()可用于屏蔽SIGTERM、SIGUSR1等可选信号,但只对当前进程有效(子进程需单独设置) - PHP 5.6 中屏蔽信号后,若未配 ticks,信号既不触发也不排队,等于“静默丢弃”;PHP 7.0+ 屏蔽后信号仍入队,只是 dispatch 时不处理 —— 行为更可预测
- 父子进程默认继承父进程的信号掩码,但 fork 后应重新调用
pcntl_sigprocmask()或pcntl_signal()显式配置,避免意外继承
子进程退出后父进程不回收,僵尸进程立即出现
无论 PHP 5.6 还是 7.0,pcntl_fork() 创建的子进程终止后,若父进程不调用 pcntl_waitpid() 或安装 SIGCHLD 处理器,就会变成僵尸进程。
- 常见错误:只在主循环里等一次
pcntl_waitpid(-1, $status, WNOHANG),但多个子进程退出时可能漏收 - 正确做法:循环调用
pcntl_waitpid(-1, $status, WNOHANG)直到返回 -1(无子进程可收),或用pcntl_signal(SIGCHLD, function() { while (pcntl_waitpid(-1, $status, WNOHANG) > 0); }) - PHP 7.0+ 中,
pcntl_async_signals(true)可能干扰SIGCHLD的常规处理逻辑,建议对SIGCHLD保持传统 ticks + dispatch 模式
真正容易被忽略的是:信号处理不是“注册完就完事”,它高度依赖执行流节奏。PHP 5.6 把节奏交给 ticks,PHP 7.0 把节奏交还给你——但你得自己握紧。



















