pcntl_signal收不到Ctrl+C是因为未启用ticks或未调用pcntl_signal_dispatch();需declare(ticks=1)或循环中手动调度,且信号回调中应仅设退出标志位,由主循环安全清理后退出。

pcntl_signal 为什么收不到 Ctrl+C?
默认情况下,pcntl_signal 不会自动处理信号,必须配合 pcntl_signal_dispatch() 或启用 ticks 才能触发回调。直接注册但不调度,等于没写——这是最常被忽略的环节。
- PHP 默认禁用 ticks,需显式声明
declare(ticks = 1);(仅限 CLI 模式) - 若用
pcntl_signal_dispatch()手动调度,必须在主循环中定期调用,否则信号积压不触发 -
SIGINT(Ctrl+C)和SIGHUP等信号只有在进程处于“可中断等待”状态时才可能被立即捕获;如果程序卡在sleep()或阻塞 I/O 中,可能延迟响应
如何让 SIGINT 触发后安全退出?
不能在信号回调里直接 exit() 或 die(),PHP 的信号处理是异步的,可能破坏资源清理逻辑。正确做法是设标志位,由主循环控制退出时机。
- 定义全局变量(如
$should_exit = false;),在信号回调中仅修改它 - 主循环检查该变量,执行 fclose、unset、db close 等清理动作后再
exit() - 避免在回调中调用
echo、var_dump等非异步信号安全函数(PHP 7.4+ 对部分函数做了加固,但仍建议只做简单赋值) - 示例片段:
declare(ticks = 1); $should_exit = false; pcntl_signal(SIGINT, function ($sig) use (&$should_exit) { $should_exit = true; }); while (!$should_exit) { // 做工作 pcntl_signal_dispatch(); // 必须有,或依赖 ticks usleep(10000); } fclose($fp); exit(0);
pcntl_signal 和 pcntl_async_signals 的区别?
pcntl_async_signals(true) 是 PHP 7.1+ 引入的替代方案,它绕过 ticks 机制,让信号真正异步触发回调,更可靠也更易用——但仅限 CLI SAPI,且不能与 declare(ticks) 混用。
- 启用后无需
declare(ticks = 1),也不用手动调用pcntl_signal_dispatch() - 仍需避免在回调中执行复杂操作;推荐只设标志、记录时间戳或写单字节到 pipe
- 兼容性注意:PHP
- 启用方式:
pcntl_async_signals(true);必须在任何pcntl_signal()调用前执行
常见陷阱:子进程、僵尸进程与信号继承
父进程注册的信号处理器不会自动继承给 fork 出的子进程,且子进程退出若不 wait,会变成僵尸进程——这和信号处理看似无关,实则常一起出问题。
立即学习“PHP免费学习笔记(深入)”;
- 子进程需单独调用
pcntl_signal()注册自己的处理器,或显式忽略(pcntl_signal(SIGCHLD, SIG_IGN)) -
SIGCHLD处理要小心:若用pcntl_waitpid(-1, $status, WNOHANG)清理,需循环直到返回 -1,否则一次只收一个子进程 - 不要在信号回调里 fork —— POSIX 不保证信号上下文中的 fork 安全,PHP 未作防护
- 调试时可用
kill -l查看系统支持信号列表,用strace -e trace=signal php script.php观察实际投递行为
pcntl_signal(SIGINT, ...) 就万事大吉,得盯着整个进程树的行为。



















