多进程并发模型如何避免僵尸进程的出现

多进程并发模型如何避免僵尸进程的出现

多进程并发模型
有一种很常见的服务端多进程并发模型,其思想是:
(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,多进程的这个细节竟然忘记了,百度了一下,重拾+记录起来。

评论关闭。