php cli进程无法响应sigterm或ctrl+c,根本原因是pcntl_signal()仅注册回调,必须配对调用pcntl_signal_dispatch()显式分发;php 7.1+需启用pcntl_async_signals(true),且须管理pcntl_sigprocmask()阻塞状态。

PHP CLI 进程无法响应 SIGTERM 或被 Ctrl+C 中断?不是信号没发,而是没注册、没分发、没解阻塞——pcntl_signal() 和 pcntl_signal_dispatch() 必须配对使用,且不能漏掉 pcntl_sigprocmask() 的状态管理。
pcntl_signal() 注册后为什么信号不触发?
常见现象:调用 pcntl_signal(SIGTERM, $callback) 后,kill -TERM $pid 没反应;或 Ctrl+C 直接终止进程,不走回调。
-
pcntl_signal()只是注册处理器,PHP 不会自动轮询信号——必须在主循环中显式调用pcntl_signal_dispatch()才能触发回调 - PHP 7.1+ 默认禁用异步信号,需先调用
pcntl_async_signals(true)(否则信号会被延迟到下一次 tick) - 若用了
pcntl_sigprocmask()阻塞信号,但没在合适时机pcntl_sigprocmask(SIG_UNBLOCK, [...]),信号将永久挂起 - 某些 SAPI(如 Apache mod_php)不支持 pcntl,仅 CLI 有效;检查
function_exists('pcntl_signal')再启用
如何安全实现“事务中不中断,结束后再处理”?
典型场景:正在写文件、提交数据库事务、上传大文件时,不能让 SIGTERM 立刻杀掉进程,但也不能完全忽略信号。
- 用
pcntl_sigprocmask(SIG_BLOCK, [SIGTERM, SIGINT])在关键段开始前阻塞信号 - 关键操作完成后,立即调用
pcntl_sigprocmask(SIG_UNBLOCK, [SIGTERM, SIGINT]) - 紧接着手动触发一次
pcntl_signal_dispatch(),确保积压信号立刻被处理 - 不要在阻塞期间长时间 sleep 或 busy-wait——信号只在 PHP 控制权返回时检查,长时间 C 层阻塞(如 curl_exec)可能跳过 dispatch
平滑重启时父子进程信号行为差异
像 xdebug-handler 或自研守护进程那样 fork 子进程后,父、子对同一信号应有不同策略。
- 父进程(管理器)应忽略
SIGINT,避免被Ctrl+C中断重启流程;可用pcntl_signal(SIGINT, SIG_IGN) - 子进程(业务逻辑)需恢复默认处理或自定义回调,确保用户可中断它;重启后调用
pcntl_signal(SIGINT, SIG_DFL) - Windows 下无 POSIX 信号,改用
sapi_windows_set_ctrl_handler(),且仅对父进程设空 handler,子进程不设——否则 Ctrl+C 全局失效 -
self::$inRestart这类状态标记必不可少,否则 fork 后父子共享静态变量值错乱,导致信号配置互相覆盖
真正容易被忽略的不是怎么注册信号,而是信号在 PHP 生命周期里的「调度时机」:它不实时、不抢占、不保证顺序。pcntl_signal_dispatch() 必须出现在你控制的每一轮循环头部,且不能被异常或 longjmp 跳过——否则信号就丢了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











