-
PHP 中的僵尸进程、孤儿进程详解
僵尸进程
当子进程运行结束,父进程仍然继续运行,但父进程没有对子进程进行回收,释放子进程占用的资源,此时子进程就成为了一个僵尸进程。
在Unix进程管理中,如果新开的子进程运行结束,父进程将会收到一个SIGCHLD信号,子进程暂时成为僵尸进程,等待父进程进行处理。如果父进程一直不处理,该进程将会一直存在,占用系统进程表项,永久成为僵尸进程。如果僵尸进程过多,导致系统没有可用的进程表项,将无法运行其他的应用程序。
<?php
$str = "hello world!" . PHP_EOL;
$pid = pcntl_fork();
if ($pid > 0) {
echo "我是主进程,id是" . getmypid() . ",子进程的pid是{$pid}" . PHP_EOL;
pcntl_async_signals(true);
pcntl_signal(SIGCHLD, function () {
echo '子进程退出了,请及时处理' . PHP_EOL;
});
while (1) { //模拟主进程一直运行
sleep(1);
}
} elseif($pid==0) {
echo "我是子进程,我的pid是". getmypid() ."\n";
}else{
echo "我是主进程,开启子进程失败\n";
}
使用ps查看僵尸进程:
ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]'
终端会输出:
Z+ 7136 7137 [php] <defunct>
当主进程退出之后,子进程将会被init接管并处理
使用 pcntl_wait 回收僵尸进程:
<?php
echo time() . PHP_EOL;
$pid = pcntl_fork();
if ($pid > 0) { //主进程代码
pcntl_wait($status); //父进程阻塞方式等待子进程的退出
echo time() . PHP_EOL;
} elseif($pid==0) {
sleep(3);
echo "我是子进程,我的pid是". getmypid() ."\n";
exit();
}else{
die("我是主进程,开启子进程失败\n");
}
1589874657
我是子进程,我的pid是16140
1589874660
pcntl_wait($status); 默认为阻塞方式回收子进程,可以设置第二个参数为 WNOHANG 即为非阻塞方式回收子进程,代码会继续往下执行,子进程退出后会自动回收。
多个子进程只需要一个pcntl_wait即可回收完成。
在父进程中,为SIGCHLD配置handler来回收:
<?php
pcntl_async_signals(true);
pcntl_signal(SIGCHLD, function () {
echo "SIGCHLD" . PHP_EOL;
pcntl_wait($status); //父进程阻塞方式等待子进程的退出
});
echo time() . PHP_EOL;
$pid = pcntl_fork();
if ($pid > 0) { //主进程代码
sleep(10);
echo time() . PHP_EOL;
} elseif($pid==0) {
sleep(3);
echo "我是子进程,我的pid是". getmypid() ."\n";
exit;
}else{
die("我是主进程,开启子进程失败\n");
}
1589874039
我是子进程,我的pid是12875
SIGCHLD
1589874040
忽略子进程结束信号,由init回收:
<?php
pcntl_async_signals(true);
pcntl_signal(SIGCHLD, SIG_IGN);
echo time() . PHP_EOL;
$pid = pcntl_fork();
if ($pid > 0) { //主进程代码
sleep(10);
echo time() . PHP_EOL;
} elseif($pid==0) {
sleep(3);
echo "我是子进程,我的pid是". getmypid() ."\n";
exit;
} else {
die("我是主进程,开启子进程失败\n");
}
1589874331
1589874331
我是子进程,我的pid是14493
孤儿进程
孤儿进程是指在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程收养,并由init进程对它们完成回收工作。
孤儿进程没有任何危害,只需要注意自己的代码逻辑即可。
<?php
$pid = pcntl_fork();
if ($pid == -1) {
die('fork error');
} elseif ($pid > 0) {
echo "爸爸先走了,儿子你好好干活" . PHP_EOL;
exit(); // 主进程在很短的时间内已经退出
} else {
// 子进程继续运行
$i = 10;
while($i) {
echo $i;
$i--;
sleep(1);
}
}
爸爸先走了,儿子你好好干活
1111111111...
孤儿进程与僵尸进程的区别
僵尸进程:一个子进程在其父进程还没有调用wait()或waitpid()的情况下退出,这个子进程就是僵尸进程。任何一个子进程在退出之后,并非马上就消失掉,而是留下一个称为僵尸进程的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在退出之后,父进程没有来得及处理,那么子进程就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。此为僵尸进程的危害,应当避免。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程收养,并由init进程对它们完成回收工作。每当一个孤儿进程出现,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()已经退出的子进程。这样,当一个孤儿进程结束其生命周期的时候,init进程就会进行回收工作。因此孤儿进程并不会有什么危害 。