【原文地址:https://blog.ti-node.com/blog...】其实大家一定要记住:PHP多进程是非常值得生产环境的高价值生产力工具。但是我觉得在正式开始吹牛之前,还有两个基本的概念需要讲一下:孤儿进程和僵尸进程。在上一篇文章中,我谈到了pcntl_fork()。我只关心叉子的生产,不管产后护理。其实,这并不符合主流价值观。而且,操作系统本身的资源是有限的,无限量生产不管照顾,操作系统将无法承受。的。孤儿进程意味着父进程在分叉出子进程后首先完成。这个问题很尴尬,因为子进程从此变得无助、无家可归、成为孤儿。从表达上来说,父进程在子进程结束之前就提前退出了,这些子进程会被init(进程ID为1)进程收养,完成对其各种数据状态的收集。init进程是Linux系统下的一个奇怪进程。此进程以普通用户权限运行,但具有超级权限。简单的说,这个进程在linux系统启动的时候做一些初始化工作,比如运行getty,比如根据/etc/inittab中设置的运行级别初始化系统等,当然还有上面提到的另一个函数:采用孤儿进程。僵尸进程是指父进程fork出子进程,子进程结束后,父进程没有调用wait或waitpid完成自己的清理工作,导致子进程的进程ID和文件描述符仍然残留在系统,极大的浪费了系统资源。因此,僵尸进程对系统的危害较大,而孤儿进程则相对没有那么严重。在Linux系统中,我们可以通过ps-aux查看进程,如果有[Z+]标记,则为僵尸进程。在PHP中,父进程通过pcntl_wait()和pcntl_waitpid()收集子进程的状态。还是需要通过代码来演示:演示和解释孤儿进程的出现,演示孤儿进程被init进程收养:0){//显示父进程的进程ID,这个函数可以是getmypid(),或者posix_getpid()echo"FatherPID:".getmypid().PHP_EOL;//让父进程停止两秒,在这两秒内,子进程的父进程ID仍然是父进程sleep(2);}elseif(0==$pid){//让子进程循环10次,每次休眠1s,然后每秒获取子进程的父进程进程IDfor($i=1;$i<=10;$i++){睡眠(1);//posix_getppid()函数是获取当前进程的父进程IDechoposix_getppid().PHP_EOL;}}else{echo"forkerror.".PHP_EOL;}运行结果如下:可以看到前两秒,子进程的父进程的进程ID是4129,但是从第三秒开始,因为父进程已经提前退出,子进程就变成了一个孤儿进程,所以init进程收养了子进程,所以子进程的父进程的进程ID变为1。演示解释僵尸进程的产生,演示僵尸进程的危害:0){//下面的函数可以改变php进程的名称cli_set_process_title('php父进程');//让主进程休息60秒sleep(60);}elseif(0==$pid){cli_set_process_title('php子进程');//让子进程休息10秒,但是进程结束后,父进程不对子进程做任何处理工作,这样子进程就会变成僵尸进程sleep(10);}else{exit('forkerror.'.PHP_EOL);}运行结果如下:执行ps-aux命令可以看到,程序在前十秒运行时,php子进程状态列为[S+],十秒后,status变为[Z+],即变为危害系统的僵尸进程。那么,问题来了?如何避免僵尸进程?PHP使用pcntl_wait()和pcntl_waitpid()来帮助我们解决这个问题。懂Linux系统编程的应该都知道,光看名字,这其实就是PHP用C语言包装了wait()和waitpid()。通过代码演示pcntl_wait()避免僵尸进程。在开始之前,先简单科普下pcntl_wait()的相关内容:该函数的作用是“等待或返回子进程的状态”。当父进程执行这个函数时,会阻塞等待子进程的状态,直到子进程因为某种原因退出或终止。也就是说,如果子进程还没有结束,父进程就等啊等。如果子进程已经结束,父进程会立即获取子进程状态。此函数返回退出的子进程的进程ID,失败时返回-1。让我们修改第二种情况的代码:0){//下面的函数可以改变php进程的名称cli_set_process_title('phpfatherprocess');//返回$wait_result,为子进程的进程号,如果子进程已经是僵尸进程则返回0//子进程的状态保存在$status参数中,可以通过$status查看pcntl_wexitstatus()等一系列函数状态信息是什么$wait_result=pcntl_wait($status);print_r($wait_result);print_r($status);//让主进程休息60秒sleep(60);}elseif(0==$pid){cli_set_process_title('php子进程');//让子进程休息10秒,但是进程结束后,父进程不会为子进程做任何处理工作,所以子进程会变成僵尸进程sleep(10);}else{exit('forkerror.'.PHP_EOL);}保存文件为wait.php,然后phpwait.php,在另一个终端通过ps-aux查看,可以看到前十秒内,php子进程处于[S+]状态,然后十秒后进程消失,即被父进程回收,没有成为僵尸进程。但是pcntl_wait()有一个很大的问题,就是阻塞。父进程只能挂起等待子进程结束或终止。在此期间,父进程什么也做不了。这不符合省钱的原则,所以pcntl_waitpid()就来了。如果pcntl_waitpid($pid,&$status,$option=0)的第三个参数设置为WNOHANG,则父进程不会阻塞并等待子进程退出或终止,否则它的行为类似于pcntl_wait().修改第三种情况的代码,但我们不添加WNOHANG来演示pcntl_waitpid()函数:0){//下面的函数可以改变php进程的名称cli_set_process_title('phpfatherprocess');//返回值保存在$wait_result中//$pid参数表示子进程的进程ID//子进程的状态保存在参数$status中//第三个如果option参数设置为常量WNOHANG,可以防止主进程被阻塞挂起。在这里,父进程会立即返回并继续执行剩下的代码。$wait_result=pcntl_waitpid($pid,$status);var_dump($wait_result);var_dump($status);//让主进程休息60秒sleep(60);}elseif(0==$pid){cli_set_process_title('php子进程');//让子进程休息10秒,但是进程结束后,父进程不对子进程做任何处理工作,这样子进程就会变成僵尸进程sleep(10);}else{exit('forkerror.'.PHP_EOL);}下面是运行结果,一个是执行php程序的终端窗口,一个是ps-aux终端窗口。其实可以看出,主进程一直阻塞到第10秒子进程退出,父进程就不再阻塞了:那么我们修改第四段代码,增加第三个参数WNOHANG,代码为如下:0){//下面的函数可以改变php进程的名称cli_set_process_title('phpfatherprocess');//返回值保存在$wait_result中//$pid参数代表子进程的进程ID//子进程的状态保存在参数$status中//设置第三个option参数为常量WNOHANG可以防止主进程阻塞挂起,父进程会立即返回并继续执行剩下的代码$wait_result=pcntl_waitpid($pid,$status,WNOHANG);var_dump($wait_result);var_dump($status);echo"不阻塞,在这里运行".PHP_EOL;//让主进程休息60秒Bellsleep(60);}elseif(0==$pid){cli_set_process_title('php子进程');//让子进程休息10秒,但是进程结束后,父进程不会再为子进程做任何处理工作,这样这个子进程就会变成僵尸进程sleep(10);}else{exit('forkerror.'.PHP_EOL);}表面是运行结果,一个是执行php程序的终端窗口,一个是ps-aux终端窗口。其实可以看出主进程阻塞了,直到第十秒子进程退出,父进程不再阻塞:问题出现了,php子进程的状态变成了【Z+],这是怎么回事?必须?回去分析代码:我们看到子进程休眠了十秒,父进程在执行pcntl_waitpid()之前没有任何休眠,不再阻塞。十秒后,进程状态无法回收。如果我们修改代码,就是在主进程的pcntl_waitpid()之前休眠15秒,这样子进程才能被回收。但是即使这样修改,仔细想想还是会有一个问题,就是子进程结束后,和父进程执行pcntl_waitpid()回收之前有五秒的时间差。在这个时间差内,php子进程也将是僵尸进程。那么,如何正确使用pcntl_waitpid()呢?毕竟这么用好像不太科学。好吧,是时候引入信号了!【原文地址:https://blog.ti-node.com/blog...】
