本文目录导读:

在 PHP 中,孤儿进程 指的是:父进程先于子进程退出,导致子进程被 init 进程(PID 1,通常是 systemd)收养的进程。
下面详细说明 PHP 中如何处理和利用孤儿进程。
PHP 如何创建子进程
PHP 主要通过 pcntl_fork() 函数创建子进程:
$pid = pcntl_fork();
if ($pid == -1) {
// 创建失败
die('fork failed');
} elseif ($pid == 0) {
// 子进程执行的代码
// 子进程的 PID 是 0(实际是子进程本身)
// 父进程 ID (PPID) 是创建它的父进程
while (true) {
echo "子进程运行中,父进程 PID: " . posix_getppid() . "\n";
sleep(1);
}
} else {
// 父进程执行的代码
// $pid 是子进程的 PID
echo "父进程结束\n";
exit(0); // 父进程先退出
}
孤儿进程的产生
上面的代码中,父进程 exit(0) 先退出,而子进程还在循环运行。
- 子进程的 PPID(父进程 ID)会变成 1(init / systemd)
- 这个子进程就成了 孤儿进程
验证方法:在父进程退出后,用 ps -ef | grep php 可以看到子进程的 PPID = 1。
处理孤儿进程的方式
不处理(默认)
$pid = pcntl_fork();
if ($pid == 0) {
sleep(10);
// 子进程继续运行,父进程已退出
}
// 父进程立即退出
exit(0);
子进程将由 init 进程接管,自动回收资源(不会变成僵尸进程)。
父进程等待子进程(避免产生孤儿)
使用 pcntl_wait() 或 pcntl_waitpid():
$pid = pcntl_fork();
if ($pid == 0) {
sleep(5);
exit(0);
}
// 父进程等待子进程结束
pcntl_wait($status);
// 此时子进程已经结束,不会产生孤儿
exit(0);
通过 signal 处理孤儿进程的退出状态
更常见的是,父进程先退出,但希望知道子进程什么时候结束:
// 父进程设置信号处理
pcntl_signal(SIGCHLD, function ($signo) {
// 子进程退出时触发
while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) > 0) {
echo "子进程 $pid 已退出\n";
}
});
$pid = pcntl_fork();
if ($pid == 0) {
sleep(5);
exit(0);
}
// 父进程可以做其他事情
sleep(10);
exit(0);
故意使用孤儿进程的场景
守护进程 (Daemon) 创建
常见模式:父进程 fork 后立即退出,让子进程被 init 接管,脱离终端控制。
function daemonize() {
$pid = pcntl_fork();
if ($pid < 0) {
die('fork failed');
}
if ($pid > 0) {
// 父进程退出
exit(0);
}
// 子进程(孤儿进程)继续运行
// 此时子进程被 init 收养
// 创建新会话,脱离终端
posix_setsid();
// 可以再 fork 一次,确保不是会话领导者
$pid = pcntl_fork();
if ($pid < 0) {
die('fork failed');
}
if ($pid > 0) {
exit(0);
}
// 子进程(真正的守护进程)运行在此
while (true) {
// 守护进程的工作
sleep(60);
}
}
与僵尸进程的区别
| 类型 | 产生原因 | 影响 | 处理方式 |
|---|---|---|---|
| 孤儿进程 | 父进程先退出 | 无害,被 init 收养并回收 | 自动处理 |
| 僵尸进程 | 子进程先退出,父进程未调用 wait() | 占用进程表项,有害 | 父进程必须 wait() 或杀死父进程 |
完整示例
// 创建孤儿进程的完整示例
$pid = pcntl_fork();
if ($pid == -1) {
die("fork failed");
}
if ($pid == 0) {
// 子进程
echo sprintf("子进程 PID: %d, 父进程 PID: %d\n",
posix_getpid(), posix_getppid());
// 模拟子进程工作
for ($i = 0; $i < 10; $i++) {
sleep(1);
// 理想情况下,父进程退出后,PPID 会变成 1
echo "工作 $i 次, 当前父 PID: " . posix_getppid() . "\n";
}
exit(0);
} else {
// 父进程
echo "父进程 PID: " . posix_getpid() . ", 子进程 PID: $pid\n";
sleep(2); // 等待 2 秒
echo "父进程即将退出...\n";
exit(0); // 父进程退出,子进程变为孤儿
}
运行结果示例:
父进程 PID: 12345, 子进程 PID: 12346
子进程 PID: 12346, 父进程 PID: 12345
工作 0 次, 当前父 PID: 12345
工作 1 次, 当前父 PID: 12345
父进程即将退出...
工作 2 次, 当前父 PID: 1 ← 变成孤儿,被 init 收养
工作 3 次, 当前父 PID: 1
...
- PHP 创建孤儿进程:父进程 fork 后先 exit()
- 孤儿进程无害:被 PID 1 的进程自动收养
- 用途:常用于创建守护进程(daemon)
- 避免僵尸进程:父进程应调用
pcntl_wait()或 catchSIGCHLD - 注意事项:PHP 需启用
pcntl和posix扩展(CLI 模式)
在实际应用中,孤儿进程的 "父进程" 变成了 init,这通常是安全的,但如果需要精确控制子进程的生命周期,建议让父进程等待子进程结束。