协程中不能直接用pcntl_signal,因其会阻塞调度器并导致信号丢失;必须使用swoole\coroutine\signal::add()在onworkerstart或coroutine\run主协程中注册,且回调内仅做轻量标记、不可阻塞,配合atomic状态检测与server->shutdown()实现优雅退出。

协程里不能直接用 pcntl_signal
传统 PHP 的 pcntl_signal 在协程中完全失效——它会阻塞整个协程调度器,甚至导致信号丢失或进程 hang 住。Swoole 协程信号监听必须走 Swoole\Coroutine\Signal,这是唯一安全路径。
常见错误现象:pcntl_signal(SIGTERM, ...) 在 Coroutine\run() 内注册后毫无反应;或触发一次后后续信号不再进入回调。
- 必须在
Coroutine\run()启动后、任何协程启动前注册信号 -
Signal::add()返回false表示失败(如信号已被占用、权限不足) - 不要在信号回调里调用阻塞函数(如
sleep()、file_get_contents()),只做轻量标记或发 Channel
如何监听 SIGTERM/SIGINT 并优雅退出
容器化部署最常踩的坑是:收到 SIGTERM 后直接 exit(),导致正在处理的请求被粗暴中断,连接未关闭、事务未回滚、资源未释放。
正确做法是“标记 + 等待 + 清理”三步:
- 用
Atomic或全局变量标记“正在退出”状态 - 主循环检测该标记,停止接受新请求(如关闭
Server的监听端口) - 等待已有协程自然结束(比如用
Channel等待所有活跃任务完成)
示例关键逻辑:
$exiting = new Swoole\Atomic(0);
Swoole\Coroutine\Signal::add(SIGTERM, function () use ($exiting) {
$exiting->set(1);
echo "SIGTERM received\n";
});
while (!$exiting->get()) {
Swoole\Coroutine::sleep(0.1);
}
// 此时可安全调用 $server->shutdown() 或清理数据库连接池
协程事件监听与 Server 生命周期怎么对齐
很多人把信号监听写在 onStart 回调里,结果发现根本收不到信号——因为 onStart 运行在 Manager 进程,而信号默认只发给 Worker 进程。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
真正有效的监听位置只有两个:
- 在
onWorkerStart中为每个 Worker 单独注册(推荐) - 在
Coroutine\run()主协程中注册(适用于无 Server 场景,如纯协程任务)
Hyperf 等框架内部正是在 onWorkerStart 里启动信号监听协程,确保每个 Worker 都能独立响应信号。如果你手写 Server,别漏掉 foreach ($serv->ports as $port) 循环注册——否则新增监听端口的信号可能被忽略。
为什么 onConnect/onReceive 里不能监听信号
协程事件回调(如 onConnect)本身运行在一个短期协程中,回调执行完协程就销毁。在这个协程里调用 Signal::add(),信号处理器只会绑定到这个瞬时协程,而不是整个 Worker 进程。
后果是:信号来了,但没协程在监听,或者监听器随协程退出而自动注销。
必须把信号监听逻辑放在长期存活的上下文中:
- Worker 启动时(
onWorkerStart) - 独立的守护协程(
go(function () { Signal::add(...); while(true) sleep(1); }))
复杂点在于:多个协程可能同时监听同一信号,Swoole 不保证回调执行顺序。如果业务需要严格串行响应,得自己用 Channel 或 Lock 做协调——这点容易被忽略。










