【原文地址:https://blog.ti-node.com/blog...】上一篇聊了pcntl_wait()和pcntl_waitpid()整篇文章,都是为了解决僵尸进程的问题,但是最后好像还是遗留了一些问题,又因为嘴巴欠了上一篇文章末尾的一个解决方案:signal。信号是一种软件中断,是一种非常典型的异步事件处理方式。在NIX系统诞生的混乱之初,信号的定义比较混乱,最重要的是不可靠,这是一个非常严重的问题。所以在后来的POSIX标准中,对信号进行了标准化,NIX的各个发行版也提供了大量的可靠信号。每个信号都有自己的名字,比如SIGTERM、SIGHUP、SIGCHLD等。在*NIX中,这些信号本质上都是整数(感兴趣的可以访问signal.h系列头文件)。产生信号的方式有很多种,下面是一些常见的:按下键盘上的某些组合键,如Ctrl+C或Ctrl+D,会产生SIGINT信号。使用posixkill调用,可以将指定的信号发送到进程。在远程ssh终端的情况下,如果您在服务器上执行被阻止的脚本,并且在阻止过程中关闭终端,则可能会生成SIGHUP信号。硬件也会产生信号,比如OOM或者遇到被0除,硬件也会发送特定的信号给进程。进程收到信号后,可以有以下三种响应:直接忽略,不响应。它通常被称为根本没有鸟。但是有两个信号是绝对不会被忽略的,一个是SIGSTOP,一个是SIGKILL,因为这两个进程向内核提供了结束进程的最终可靠途径。捕获信号并做出相应的响应。具体响应可由用户通过程序自定义。系统默认响应。大多数进程遇到信号后,如果用户没有自定义响应,将采用系统默认响应,大多数系统默认响应是终止进程。用人的话来说就是,如果你是一个过程,你正在干活,突然施工队用扩音器对你喊:“该吃饭了!”,于是你放下手头的工作去吃饭了。你正在干活,突然施工队用扩音器对着你喊:“发工资了!”,于是你放下手头的活去领工资。你正在干活,突然施工队用扩音器对你喊:“有人找你!”,于是你放下手头的活,看看谁找你,干什么。当然,如果你任性,可以忽略喇叭的内容,也就是忽略信号。你也可以更任性。当扩音器对着你喊“吃”的时候,你不去吃饭,你去睡觉,这些都由你决定。而且在工作的过程中,你永远不会因为想等待某个信号而停止工作,等待信号,但是信号可能随时随地来,你只需要在这个时候做出相应的反应,所以,一个信号是一种软件中断和异步处理事件的方式。回到上面提到的问题,父进程在子进程结束前就已经调用了pcntl_waitpid(),导致子进程结束后仍然成为僵尸进程。其实就是在父进程中循环不断调用pcntl_waitpid()来解决。大致代码如下:$pid=pcntl_fork();if(0>$pid){exit('forkerror.'.PHP_EOL);}elseif(0<$pid){//在父进程中cli_set_process_title('php父进程');//父进程不断while循环重复执行pcntl_waitpid(),试图解决退出的子进程while(true){sleep(1);pcntl_waitpid($pid,&$status,WNOHANG);}}elseif(0==$pid){//在子进程中//子进程休眠3秒后直接退出cli_set_process_title('phpchildprocess');睡眠(20);exit;}下图是运行结果:分析一下结果,我执行了ps-aux|grepphp三次查看两个php进程。第一次:子进程在休眠,父进程还在循环中。第二次:子进程已经退出,父进程还在循环中,但是代码还没有执行到pcntl_waitpid(),所以子进程在子进程出口和父进程之间的间隙变成了僵尸进程进程执行回收。第三次:此时父进程已经执行了pcntl_waitpid(),回收退出的子进程,释放pid等资源。但是这样的代码有一个缺陷。实际上,当子进程已经退出时,主进程还在做whilepcntl_waitpid()回收子进程。这是一件很奇怪的事情,不符合社会主义主流价值观。不低碳不节能,代码也不优雅丑陋。因此,应该考虑更好的方法。那么,我们在本文开头提到很久的信号终于登场了。现在我们思考一下为什么信号能解决“不低碳、不节能、代码不优雅、不好看”的问题。当子进程退出时,它会向父进程发送一个信号,称为SIGCHLD。一旦父进程收到这个信号,就可以采取相应的回收动作,即执行pcntl_waitpid(),从而解决僵尸进程并返回……看来我们的代码优雅美观,节能环保。梳理一下进程,子进程向父进程发送SIGCHLD信号对人来说是透明的,也就是说我们不需要关心。但是,我们需要安装一个处理器来响应SIGCHLD信号给父进程。此外,我们需要让这些信号处理器运行起来。安装不运行是一件很尴尬的事情。那么,php中用来给进程安装信号处理器的函数就是pcntl_signal(),而让信号处理器运行起来的函数就是pcntl_signal_dispatch()。pcntl_signal(),安装一个signalhandler,具体是pcntl_signal(int$signo,callback$handler[,bool$restart_syscalls=true]),参数signo是信号,callback是响应信号的代码段,返回一个布尔值。pcntl_signal_dispatch(),调用pcntl_signal()安装的各个handler等待信号,参数为void,返回bool值。下面结合新引入的两个函数来解决楼上难看的代码:$pid=pcntl_fork();if(0>$pid){exit('forkerror.'.PHP_EOL);}elseif(0<$pid){//在父进程中//为父进程安装一个SIGCHLD信号处理程序pcntl_signal(SIGCHLD,function()use($pid){echo"receivedchildprocessexit".PHP_EOL;pcntl_waitpid($pid,$status,WNOHANG);});cli_set_process_title('php父进程');//父进程继续while循环重复执行pcntl_waitpid(),从而尝试解决退出的子进程while(true){sleep(1);//注释掉旧代码并改用pcntl_signal_dispatch()//pcntl_waitpid($pid,&$status,WNOHANG);pcntl_signal_dispatch();}}elseif(0==$pid){//在子进程中//子进程休眠3秒直接退出cli_set_process_title('phpchildprocess');睡眠(20);exit;}运行结果如下:【原文地址:https://blog.ti-node.com/blog...】
