PHP多进程 - 关于进程,父子进程,僵尸进程,孤儿进程

一、进程信息

当你运行一个PHP脚本,那么就会创建一个进程。

<?php
sleep(300);
?>

运行 php demo1.php

查看进程ID

ps -ef | grep demo1
21952

查看进程的全部内容

ls /proc/21952

在这里插入图片描述
fd 目录保存了进程打开的文件描述符
status 文件存储了进程的状态信息

cat /proc/21952/status

在这里插入图片描述
可见每创建一个PHP进程,操作系统就要创建这么多文件,以及分配18M左右的内存,所以创建进程的开销还是比较大的。

二、父子进程

很好奇,为什么它有父进程呢,进程1622是啥?
在这里插入图片描述
因为我是用xshell连的虚拟机,每个窗口都是一个独立的连接,sshd 服务会为每一个连接创建一个单独的进程,用来处理用户的交互,进程名为 root@pts/xxxx为编号(从0开始),代表root账户登录打开的第几个窗口,我这里打开了4个窗口,所以你可以看到 root@pts/0,root@pts/1,root@pts/2,root@pts/3 四个进程。然后ssh进程会启动bash进程,用来解析并执行命令,我发送的每个命令都是由bash进程根据需要来forl / clone 出子进程(21952)来执行的,进程ID=1是操作系统初始化的进程,是所有进程的祖先 。

过程如下:

1、ID=1 --> sshd进程 = 997
2、客户端建立ssh连接后,sshd = 997 --> ssh进程 = 1614
3、ssh进程启动bash进程,ssh 进程 = 1614 --> bash进程 = 1622
3、bash解析并执行用户发送的命令(比如 php demo1.php),bash = 1622 --> php进程 = 21952

三、孤儿进程

那么,如果我关闭了这个终端会怎样?

修改代码

<?php
while(true){
	sleep(1);
}
?>

启动后台运行:nohup php demo1.php > /dev/null 2>&1 &
在这里插入图片描述
关闭运行demo1.php的终端后
在这里插入图片描述
可见,关闭终端窗口后,就意味着断开ssh连接,对应的ssh进程就会结束。如果程序正确设置了后台运行,ssh进程先于PHP进程结束了,那么PHP进程就会成为孤儿进程。而孤儿进程会被ID=1的进程接管,成为它的子进程。

最后别忘了 kill -9 22225

不要被此处的例子误导了,正常情况下,断开ssd连接之后,该连接运行的前台进程都会收到SIGHUP信号被终止。

四、僵尸进程

当一个父进程以fork / clone系统调用建立一个子进程后,如果父进程晚于子进程结束,那么在子进程结束的时候会通过操作系统向父进程发送信号,父进程要等着这个信号并接收它,如果父进程没有这么做的话,那么这个子进程就变成僵尸进程。

僵尸子进程已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态信息供其他进程收集,除此之外,僵尸进程不再占有任何存储空间。他需要他的父进程来为他收尸,如果他的父进程没有安装SIGCHLD信号处理函数调用wait 或 waitpid() 等待子进程结束,也没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时候父进程结束了,那么init进程会自动接手这个子进程,为他收尸,他还是能被清除掉的。但是如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是系统中为什么有时候会有很多的僵尸进程。

演示

<?php
$pid = pcntl_fork();
if ($pid > 0) {
    cli_set_process_title('php father demo1');
    echo '子进程ID:', $pid, PHP_EOL;
    sleep(600);
} else if (0 == $pid) {
    cli_set_process_title('php child demo1');
    sleep(10);
} else {
    exit('fork error.' . PHP_EOL);
}
?>

运行 php demo1.php
子进程ID:22612

ps -ef | grep demo1
在这里插入图片描述
10秒之后
在这里插入图片描述
已经看不见子进程了,说好的僵尸进程去哪了?
直接使用 ps -aux 查看,找到了

root     22612  0.0  0.0      0     0 pts/3    Z+   18:50   0:00 [php] <defunct>

原来,一个进程变成僵尸进程后,其进程名称会变成 defunct父进程ID变成0
查看父进程ID

[root@localhost proc]# ps -ef | grep 22612
root     22612 22611  0 18:50 pts/3    00:00:00 [php] <defunct>

综上所述,查看僵尸进程的方式

[root@localhost proc]# ps -ef | grep defunct
root     22756 22755  0 19:11 pts/3    00:00:00 [php] <defunct>

或者使用命令 ps -A -ostat,ppid,pid,cmd |grep -e '^[Zz]'

使用top命令也可以查看僵尸进程的数量,查看 zomble 的数量
在这里插入图片描述
如何杀死僵尸进程?

事实证明 kill -9 是没办法杀死的。

需要找到该defunct僵尸进程的父进程,将该进程的父进程杀掉,则此defunct进程将自动消失。

如何预防僵尸进程?

以上介绍的只是在发现了僵尸进程之后,如何去杀死它。那么,如何预防僵尸进程的产生呢?。

  • 在父进程创建子进程之前,就向系统申明自己并不会对这个子进程的exit动作进行任何关注行为,这样的话,子进程一旦退出后,系统就不会去等待父进程的操作,而是直接将该子进程的资源回收掉,也就不会出现僵尸进程了。具体的办法就是,在父进程的初始化函数中,调用这个函数:signal(SIGCHLD,SIG_IGN)。
  • 如果上述语句没来得及调用,也有另外一个办法。那就是在创建完子进程后,用waitpid等待子进程返回,也能达到上述效果,如果你需要等待子进程的执行结果,那么只能使用waitpid操作。
  • 如果上述两个办法都不愿意采用,并且子进程可能需要长期运行的时候,可以使用此方法:在父进程创建子进程的时候,连续调用两次fork / clone,而且使紧跟的子进程直接退出(该子进程还是需要被回收),使其孙子进程成为孤儿进程,从而init进程将代替父进程来接手,负责清除这个孤儿进程。于是,父进程就无需进行任何的清理行为,系统会自动处理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值