在 PHP 面试中,多进程编程是一个高频且容易拉开差距的考点。大多数 PHP 开发者日常习惯于 Web 的短生命周期模式,对 pcntl 扩展和进程池设计缺乏系统理解。本文将从面试角度出发,梳理 pcntl 核心能力,并给出进程池的实现思路。
为什么 PHP 需要多进程?
PHP 的传统运行模式是「一个请求一个进程」,由 PHP-FPM 管理。但在 CLI 场景下,比如队列消费、数据批量处理、定时任务,单进程串行执行往往无法充分利用多核 CPU。此时就需要主动创建子进程来并行处理任务。
PHP 提供了 pcntl(Process Control)扩展来实现 Unix 风格的多进程编程。需要注意:pcntl 仅在 CLI 模式下可用,且不支持 Windows。
pcntl 核心函数速览
面试中常被问到的几个关键函数:
pcntl_fork():创建子进程。父进程返回子进程 PID,子进程返回 0,失败返回 -1。pcntl_wait()/pcntl_waitpid():阻塞等待子进程退出,并回收资源,避免僵尸进程。pcntl_signal():注册信号处理器,常用于优雅退出。pcntl_exec():在子进程中执行新程序,替换当前进程映像。posix_getpid()/posix_getppid():获取当前进程和父进程 ID。
一个最简 fork 示例:
$pid = pcntl_fork();
if ($pid == -1) {
exit("fork failed");
} elseif ($pid == 0) {
// 子进程逻辑
exit(0);
} else {
// 父进程逻辑
pcntl_waitpid($pid, $status);
}
僵尸进程与孤儿进程
这是面试必问的概念:
- 僵尸进程:子进程已退出,但父进程未调用
wait回收其退出状态,进程表中仍保留条目。 - 孤儿进程:父进程先于子进程退出,子进程被 init(PID 1)收养。
避免僵尸进程的标准做法是父进程调用 pcntl_waitpid(),或注册 SIGCHLD 信号处理器异步回收:
pcntl_signal(SIGCHLD, function () {
while (pcntl_waitpid(-1, $status, WNOHANG) > 0) {}
});
注意:PHP 中信号处理需要配合 pcntl_signal_dispatch(),或在循环中调用 declare(ticks=1)。
进程池要解决什么问题
如果每个任务都 fork 一次,进程创建和销毁的开销会非常大。进程池的核心思想是:预先创建固定数量的工作进程,通过队列分发任务,进程常驻复用。
一个典型的进程池包含三个角色:
- Master 进程:负责创建、监控 Worker,接收任务并分发。
- Worker 进程:从队列取任务并执行,执行完继续等待下一个任务。
- 任务队列:可以是内存队列、Redis 队列或消息队列。
进程池实现思路
1. 进程创建与监控
Master 启动时 fork 出 N 个 Worker,并记录每个 Worker 的 PID。通过 SIGCHLD 信号感知 Worker 退出,及时补充新 Worker,保证池中进程数量稳定。
for ($i = 0; $i < $workerNum; $i++) {
$pid = pcntl_fork();
if ($pid == 0) {
workerLoop();
exit(0);
}
$workers[$pid] = true;
}
2. 任务分发
常见有两种模式:
- 抢占式:所有 Worker 竞争同一个队列(如 Redis 的
BRPOP),谁抢到谁执行。实现简单,负载均衡好。 - Master 派发式:Master 从队列取任务,通过 socket pair 或管道推送给空闲 Worker。控制更精细,但实现复杂。
面试中推荐先答抢占式,再补充派发式的优劣。
3. 优雅退出
Master 收到 SIGTERM 时,不应立即杀死 Worker,而应:
- 停止接收新任务;
- 向所有 Worker 发送信号,让它们处理完当前任务后退出;
waitpid回收所有子进程后 Master 再退出。
pcntl_signal(SIGTERM, function () use ($workers) {
foreach ($workers as $pid => $_) {
posix_kill($pid, SIGTERM);
}
});
4. 防止进程泄漏
Worker 长时间运行可能因内存泄漏导致占用飙升。常见做法是设置最大处理任务数,达到阈值后 Worker 主动退出,由 Master 拉起新进程。
面试加分点
- 说明
pcntl_fork()后文件描述符、内存是写时复制(COW),父子进程不共享变量。 - 提到
Swoole/Workerman已经封装了进程管理,生产环境优先用成熟方案。 - 强调信号处理在 PHP 中需要
pcntl_signal_dispatch,否则不会立即触发。 - 对比多进程与多线程:PHP 的
pthreads已不推荐,多进程更稳定、隔离性更好。
总结
掌握 pcntl 多进程编程,关键在于理解 fork 语义、信号机制和进程回收。进程池的本质是用常驻进程 + 队列替代频繁 fork,从而降低开销、提升吞吐。面试中能清晰讲出 Master-Worker 模型、优雅退出和防泄漏策略,基本就能拿到这一题的高分。
未经允许不得转载:任鹏个人博客 » PHP 面试精讲:PHP 多进程编程之 pcntl 与进程池实现思路

