swoole中pcntl_signal收不到信号是因为启动时调用swsignal_none()阻塞了所有worker进程信号;必须用swooleprocess::signal在start前于主线程注册,且回调需同步非阻塞。

pcntl_signal在Swoole里根本收不到信号
不是你注册错了,是 Swoole 启动时就调用 swSignal_none() 把所有线程(包括 Worker 进程)的信号全部 SIG_BLOCK 了。内核发来的 SIGTERM、SIGCHLD 等根本不会递达 PHP 用户层,pcntl_signal 注册的回调压根没机会执行。
典型现象:kill -SIGTERM {pid} 后进程无反应,日志没输出,协程照常跑;用 strace -p {pid} -e trace=rt_sigaction,rt_sigprocmask 能看到信号被阻塞,且回调地址为空。
验证方式:在 Swoole Server 启动前,单独运行一段纯 CLI + pcntl_signal 的代码,它能正常触发——说明 PHP 层本身没问题,问题出在 Swoole 的信号接管机制上。
SwooleProcess::signal 是唯一有效的注册路径
SwooleProcess::signal 内部调用的是 swSignal_add,把回调写进全局 signals[] 数组,并通过 sigaction 绑定到主线程(Manager/Master),绕过了被阻塞的 Worker 线程。
关键约束条件:
- 必须在
$server->start()之前注册,否则主线程 eventloop 已启动,信号注册失效 - 回调函数必须是同步、非阻塞逻辑,不能调用
SwooleCoroutine::sleep()或$server->shutdown() - 不能在
SwooleCoroutine::create()里调用它——必须在主协程/主线程上下文执行
正确写法示例:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
SwooleProcess::signal(SIGTERM, function () {
echo "SIGTERM received
";
SwooleG::set(['shutdown' => true]);
});
$server->start();
子进程退出信号要单独监听 SIGCHLD
父进程想自动回收子进程,不能只靠 pcntl_signal(SIGCHLD, ...),它在 Swoole 环境下同样无效。
必须用 SwooleProcess::signal(SIGCHLD, ...),并在回调里配合非阻塞 SwooleProcess::wait(false):
SwooleProcess::signal(SIGCHLD, function ($sig) {
while ($ret = SwooleProcess::wait(false)) {
echo "Child {$ret['pid']} exited with code {$ret['code']}
";
// 可在此拉起新进程
}
});
注意:SwooleProcess::wait() 默认是阻塞的,传 false 才是非阻塞轮询,避免卡住 eventloop。
管道通信和信号不能混用同一个进程模型
如果子进程启用了 $redirect_stdin_stdout = true,它的 STDIN/STDOUT 已被重定向到管道,此时再往该子进程发信号(比如 kill -USR1 $pid),信号会送达,但子进程若没用 SwooleProcess::signal 监听,就直接忽略——它不像主进程那样默认注册了 SIGCHLD 处理器。
更隐蔽的问题:子进程内部若调用 swoole_event_add($process->pipe, ...) 做异步读取,那它已进入 eventloop,此时信号注册必须在 swoole_event_add 之前完成,否则 eventloop 启动后无法再安全注册信号处理器。
一句话总结:Swoole 的信号体系是分层的——Master 主线程管全局信号,Worker 进程默认不参与信号分发,子进程得自己用 SwooleProcess::signal 显式注册,且时机、上下文、回调内容都卡得很死。










