多进程并发模型如何避免僵尸进程的出现
多进程并发模型
有一种很常见的服务端多进程并发模型,其思想是:
(1)服务端主进程建立监听;
(2)当有客户端连接到服务端时,主进程fork一个子进程对新建立连接进行处理;
(3)子进程完成读取数据,逻辑处理,返回数据的工作;
(4)子进程退出;
其伪代码如下:
socket(); //创建socket bind(); //绑定 listen(); //监听 while(1) { accept(); //一个客户端连上来 if(fork()==0) //创建子进程处理 { read(); //读数据 process(); //逻辑处理 write(); //写数据 exit() ; //子进程终止 } }
存在的问题
上述伪代码存在一个这样的问题,子进程exit后成为了尸体,父进程仍然在执行,如果不做对子进程进行收尸处理,子进程则会变成一个僵尸进程(ps查看进程会显示为defunct)。
处理办法
为了避免子进程变成僵尸进程,通常有两种处理方案:
(1)杀死父进程:父进程被杀死后,子进程的尸体会由1号进程init来收,收尸的过程就是一些资源的清理,例如在进程表中将尸体占位去除掉。
(2)父进程收尸:父进程执行wait或者waitpid来对子进程进行收尸;
对于上述伪代码示例,方法(1)不可行,因为父进程是服务的主进程,不能被杀死(其实可以fork两次,这样父进程就可以被杀死了,不展开),那么只能由父进程来执行wait/waitpid收尸了。
改进后的伪代码如下:
socket(); //创建socket bind(); //绑定 listen(); //监听 while(1) { accept(); //一个客户端连上来 if(fork()==0) //创建子进程处理 { read(); //读数据 process(); //逻辑处理 write(); //写数据 exit() ; //子进程终止 } waitpid(); //父进程执行收尸 }
问题又来了
waitpid是个阻塞操作,如果子进程没有处理完,父进程是卡在waitpid上的,这就会出现这样的现象:父进程accept客户端来的一个连接,fork一个子进程进行处理,同时waitpid等待子进程结束;结束后再accept客户端来的下一个连接,,,,,,原意是一个并发模型,实际却成了并发处理,实在恼人。
解决办法
子进程在exit的时候,内核会发一个SIGCHLD信号,父进程可以监听这个信号,来做waitpid的工作,而不是阻塞着等待子进程的结束。
改进后的伪代码如下:
socket(); //创建socket bind(); //绑定 listen(); //监听 signal(SIGCHLD, SigChild); //捕获SIGCHLD while(1) { accept(); //一个客户端连上来 if(fork()==0) //创建子进程处理 { read(); //读数据 process(); //逻辑处理 write(); //写数据 exit() ; //子进程终止 } waitpid(); //父进程执行收尸 } void SigChild(int sigNum) { waitpid(); }
末了
文章的缘起是一个实习生做入职大作业,使用了多进程来实现一个支持并发的web-server出现的上述问题。
搞了好些年epoll,多进程的这个细节竟然忘记了,百度了一下,重拾+记录起来。
评论关闭。