pcntl_signal在协程中失效,因其同步阻塞且依赖手动轮询,而swoole协程调度器不调用pcntl_signal_dispatch;信号是进程级的,协程共享同一信号上下文导致竞争覆盖;swoole默认屏蔽worker进程信号,仅master/manager可接收,故必须用swoole\coroutine\signal(协程安全)或swoole\process::signal(需事件循环已启动且主线程注册)。

协程中不能直接用 pcntl_signal,必须用 Swoole\Coroutine\Signal 或 Swoole\Process::signal(后者需配合事件循环)——否则信号会丢失或只在主协程生效。
为什么 pcntl_signal 在协程里不工作
PHP 原生的 pcntl_signal 是同步、阻塞式注册,依赖 pcntl_signal_dispatch() 主动轮询,而 Swoole 协程调度器接管了控制流,不会主动调用该函数。更关键的是:信号是进程级的,但协程没有独立的信号掩码,多个协程共享同一信号处理上下文,导致竞争和覆盖。
- 现象:
pcntl_signal(SIGTERM, $cb)注册后,进程收到信号却无回调执行,或只触发一次就失效 - 根本原因:Swoole 的事件循环未集成 PCNTL 信号分发机制,且协程切换时上下文不保存信号状态
- 兼容性:即使在非协程环境,
pcntl_signal与 Swoole 的Event::loop也存在冲突,官方明确不推荐混用
Swoole\Coroutine\Signal 是协程安全的首选
这是 Swoole 4.5+ 引入的专用协程信号通道,底层基于 signalfd(Linux)或 kqueue(macOS),完全异步、非阻塞,每个协程可独立监听、接收信号,互不干扰。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 使用场景:需要在某个业务协程内响应
SIGINT/SIGTERM并做清理(如关闭数据库连接、退出循环) - 参数差异:
new Swoole\Coroutine\Signal([SIGINT, SIGTERM])支持传入信号数组,但不能传SIGHUP等被 Swoole 内部占用的信号 - 性能影响:零额外线程、无轮询开销,信号到达即唤醒对应协程,延迟在微秒级
- 示例:
<?php Swoole\Coroutine\run(function () {
// 创建信号通道,监听 SIGINT 和 SIGTERM
$signal = new Swoole\Coroutine\Signal([SIGINT, SIGTERM]);
// 启动业务协程
go(function () use ($signal) {
echo "Worker started\n";
while (true) {
Swoole\Coroutine::sleep(1);
echo "Working...\n";
}
});
// 单独协程监听信号
go(function () use ($signal) {
echo "Waiting for signal...\n";
$sig = $signal->pop(); // 阻塞直到收到信号
echo "Got signal: {$sig}\n";
// 执行清理逻辑(注意:此处仍是协程上下文)
Swoole\Coroutine::sleep(0.1); // 模拟释放资源
echo "Cleanup done, exiting.\n";
exit(0);
});
});
Swoole\Process::signal 仍可用,但有硬性前提
这个接口本质是给「主进程」注册信号处理器,不是协程原生方案。它能在协程环境中工作,**前提是事件循环正在运行**(即已调用 Swoole\Event::wait() 或处于 Swoole\Coroutine\run() 中),否则信号回调永远不会被触发。
- 常见错误:在
go()内部调用Swoole\Process::signal,但没确保事件循环启动 —— 回调永远不执行 - 适用场景:全局统一处理(如所有 Worker 进程收到
SIGUSR1时 reload 配置),不适合单个协程粒度控制 - 陷阱:回调函数内不能调用任何可能挂起的协程 API(如
Swoole\Coroutine::sleep),否则会卡死整个事件循环 - 正确写法示例:
<?php Swoole\Coroutine\run(function () {
// 必须在事件循环启动后注册
Swoole\Process::signal(SIGTERM, function ($sig) {
echo "Global SIGTERM received\n";
// 只能调用同步函数,如 file_put_contents、echo、exit
exit(0);
});
go(function () {
while (true) {
echo "Service running...\n";
Swoole\Coroutine::sleep(2);
}
});
});
容器化部署时最易忽略的点
在 Docker/K8s 中,SIGTERM 默认发给 PID 1 进程。如果你用 php server.php 直接启动,那没问题;但若用了 supervisord、tini 或 shell wrapper(如 sh -c 'php server.php'),信号可能无法透传到 PHP 进程,导致 Swoole\Coroutine\Signal 完全收不到信号。
- 验证方法:进容器执行
kill -TERM <pid></pid>,看是否触发;再用docker stop测试,两者行为必须一致 - 解决方案:Dockerfile 中用
EXEC启动(ENTRYPOINT ["php", "server.php"]),或显式启用--init参数 - 额外注意:
Swoole\Coroutine\Signal不支持 Windows,生产环境务必确认 OS 兼容性










