在 Swoole 中管理进程,其实就是在避免三种典型问题:僵尸进程堆积、子进程意外退出无人接管、主服务停止时子进程残留。要处理干净,必须根据使用场景区分对待——单进程、自定义进程、进程池、Task 模块,这四类的生命周期控制方式完全不同,混用就会出问题。

单个 Swoole Process 的创建与回收
直接实例化 SwooleProcess 时,子进程与父进程完全解耦,如果不手动回收,它立刻就会变成僵尸进程。这一点新手最容易忽略。
具体操作分三步:
- 第一步:用匿名函数定义子进程逻辑。注意不能在回调内直接
exit()或die(),必须调用$worker->exit()。 - 第二步:调用
$process->start()获取 PID。此时子进程已启动,但父进程如果不等待或监听,会立刻退出并遗弃子进程。 - 第三步:在父进程中调用
swoole_process::wait()阻塞等待——这个方法只能回收一个子进程。如果需要非阻塞回收,必须配合pcntl_signal(SIGCHLD, ...)和swoole_process::wait(false)组合使用。
这一步操作起来不算复杂,但漏掉 signal 注册或 wait 调用,5 秒后就能在 ps aux | grep defunct 里看到僵尸进程,别问怎么知道的。
将自定义进程接入 Swoole Server 生命周期
当 SwooleHttpServer 或 SwooleWebSocketServer 已经存在时,所有后台任务进程必须通过 addProcess() 注入,否则 server->shutdown() 时子进程不会被终止。这是最容易被搞混的一个点——很多人以为开了 Server 就能自动管理所有子进程。
方法一:传入 SwooleProcess 对象实例
$http = new SwooleHttpServer("0.0.0.0", 9501);
$taskProc = new SwooleProcess(function($proc) {
while(true) { // 消费消息
sleep(3);
}
});
$http->addProcess($taskProc); // 关键:必须走这一步
方法二:使用闭包快捷注册(Swoole ≥ 4.8.0)
$http->addProcess(new SwooleProcess(function($proc) {
while(true) {
$proc->write("tick\n");
sleep(1);
}
}));
注意:addProcess() 必须在 $http->start() 之前调用,否则进程不会被纳入管理,SIGTERM 信号也无法触达。这个顺序问题,调试时很容易被忽略。
用进程池管理器统一调度多工作进程
当需要固定数量的常驻子进程,并且希望支持自动拉起、信号重启、任务投递时,进程池是更省心的选择。典型场景包括日志归档、定时清理、消息广播。
使用流程很清晰:
- 创建 PoolsManager 实例,指定 worker 数
- 调用
addProcess()注册每个子进程类,传入完整命名空间路径 - 在子进程类的
run()方法中写死循环逻辑——注意不要sleep(0)或空耗 CPU - 启动后发送
kill -USR2 $MASTER_PID可触发全部 worker 逐个重启
需要特别警惕的是:进程池不兼容 Swoole Server 的 addProcess(),二者不可嵌套使用。如果你已经在 HTTP Server 中运行,应该改用 TaskWorker 机制替代进程池。
用 TaskWorker 实现 I/O 密集型任务异步卸载
当任务本身不消耗 CPU,但需要等待数据库、Redis 或 HTTP 响应时,优先选 TaskWorker 而非 Process。原因很简单:协程 + Task 更轻量,而且 自动回收——你不需要关心 PID、管道、信号、wait,这些全部由 Swoole 底层封装处理。
启用步骤:
- 设置
task_worker_num参数启用 Task 机制 - 在
onReceive或onMessage中调用$server->task($data)投递任务 - 在
onTask回调中执行实际逻辑,完成后调用$server->finish($result)
整个过程就像在说“把活儿扔给后台,有人帮你收尾”,写起来也最清爽。