关于master-worker多进程管理的几种方式
作者 斯人 | 发布于 2017 年 6 月 14 日

最近工作中有一个多进程的场景,master、worker的方式,但是在实际开发中发现,主进程管理子进程的方式有多种,通常有 while、wait、信号三种模式,正好借此机会做一次总结

while模式

顾名思义,该模式需要master用while模式阻塞,劣势非常明显,如果直接while(1)会导致CPU飙高


#include <stdio.h> #include <sys/signal.h> int main(int argc, char **argv){ signal(SIGINT,mkc_sig_handler) mkc_fork_worker(); while(1){ #阻塞等待主进程接受信号并发送给相关子进程 ...... #增加延迟,给CPU调度空间 usleep(500); } }

上面的伪代码有严重的缺陷,while(1)直接回让CPU超高,这是因为 CPU需要系统通知它没有什么可做的,不需要占用时间线,从而调度到其他进程,但是while(1)会一直告诉CPU 我很忙很忙,你要一直给我做事情,那CPU就没有机会调度到其他进程,这个时候可以加一个usleep,从而给CPU空闲的时间

wait模式

该模式用waitpid函数,将主进程挂起,注意这里是系统挂起,主进程等待信号或者子进程结束时才会返回,这种模式开发简单也易于维护

pid_t waitpid(pid_t pid,int * status,int options);

#include <stdio.h>

int main(int argc ,char **argv){
    int status;
    pid_t pid;
    while((pid = waitpid(-1,&status,WNOHANG | WUNTRACED) > 0){

              ......
        char buf[128];

        child = mkc_reap_children();
        #正常退出 
        if(WIFEXITED(status)){

            snprintf(buf,sizeof(buf), "pid [%d] exited with code %d",(int)pid ,WEXITSTATUS(status));
         # 段错误退出
        }else if(WIFSIGNALED(status)){

            const char *have_core = WCOREDUMP(status) ? " - core dumped" :"";
            snprintf(buf,sizeof(buf), "pid [%d] on signal %d(%s)",(int)pid, WTERMSIG(status),have_core);
        #暂停
        }else if(WIFSTOPPED(status)){

            snprintf(buf,sizeof(buf), "child %d stooped for tracing", (int)pid);
            err_code = MKC_LOG_NOTICE;
        }
        mkc_write_log(err_code,buf);
        #检查还有没有活跃的子进程
        live = mkc_reap_children();

        if(live == 0){

            mkc_write_log(MKC_LOG_NOTICE, "there no child process, mkc master process will stop.", (int)pid);
            exit(1);
        }
    }
}

参数说明

pid

  • pid<-1 等待进程组识别码为 pid 绝对值的任何子进程
  • pid=-1 等待任何子进程,相当于 wait()
  • pid=0 等待进程组识别码与目前进程相同的任何子进程
  • pid>0 等待任何子进程识别码为 pid 的子进程

status

进程的退出有多种状态,比如正常退出、致命错误退出、进程暂停等
子进程的结束状态值会由参数 status 返回,而子进程的进程识别码也会一起返回,一般来讲,*一定要根据status进行子进程的管理工作

  • WIFEXITED(status)如果若为正常结束子进程返回的状态,则为真;对于这种情况可执行WEXITSTATUS(status),取子进程传给exit或_eixt的低8位。

  • WEXITSTATUS(status)取得子进程 exit()返回的结束代码,一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。

  • WIFSIGNALED(status)若为异常结束子进程返回的状态,则为真;对于这种情况可执行WTERMSIG(status),取使子进程结束的信号编号。
  • WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用 WIFSIGNALED 来判断后才使用此宏。
  • WIFSTOPPED(status) 若为当前暂停子进程返回的状态,则为真;对于这种情况可执行WSTOPSIG(status),取使子进程暂停的信号编号。
  • WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用 WIFSTOPPED 来判断后才使用此宏。

options

  • WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。
  • WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。WIFSTOPPED(status)宏确定返回值是否对应与一个暂停子进程

信号管理模式

该模式跟waitpid类似,上一种由系统管理信号,对进程挂起,而这种信号将直接由开发人员来管理

#include <stdio.h>
void mkc_signal_handler(int sig){

    switch(sig){
        case SIGHUP: //reload

            mkc_sigreload = 1;
            break;
        case SIGINT:
        case SIGKILL:
        case SIGQUIT:
            mkc_sigquit = 1;
            break;

        case SIGCHLD:
            mkc_sigchld = 1;
            break;
        case SIGALRM:
            mkc_sigalrm = 1;
            break;
        case SIGTERM:
            mkc_sigterm = 1;
            break;
    }
}
void mkc_init_signal(){
    struct sigaction sa;

    sigemptyset(&sa.sa_mask);

    sa.sa_flags = 0;
    sa.sa_handler = mkc_signal_handler;

    sigaction(SIGINT,&sa,NULL);
    sigaction(SIGCHLD,&sa,NULL);
    sigaction(SIGALRM,&sa,NULL);
    sigaction(SIGQUIT,&sa,NULL);
    si
int main(int argc ,char **argv){

     #设置主进程的信号管理
     mkc_init_signal();
    sigset_t set;
    sigemptyset(&set);
    //sigaddset(&set,SIGINT);
    sigaddset(&set,SIGCHLD);
    sigaddset(&set,SIGALRM);

    sigaddset(&set,SIGQUIT);
    //reload
    sigaddset(&set,SIGHUP);

    if(sigprocmask(SIG_BLOCK,&set,NULL) == -1){

        mkc_write_log(MKC_LOG_ERROR,"sigprocmask() error.");
    }

    sigemptyset(&set);

    int live = 0;
    while(1){

        live = mkc_reap_children();

        if(!live && (mkc_sigterm || mkc_sigquit)){

            //wait worker exited.
            sleep(3);

            if(mkc_sigreload){
                mkc_sigreload = 0;
                mkc_pctl_execv();
            }
            mkc_master_process_exit();
        }

        sigsuspend(&set);
        if(mkc_sigreload == 1){

            //发送reload
            mkc_signal_worker_process(SIGHUP);
            continue;
        }
        if(mkc_sigterm == 1){

            mkc_signal_worker_process(SIGKILL);
            continue;
        }
        if(mkc_sigquit == 1){

            mkc_signal_worker_process(SIGQUIT);
            continue;
        }
   }
}

这种模式也很简单,设置信号后,用sigsuspend来挂起进程,等待信号,无论waitpid还是信号管理两种优势劣势相差并不大,信号模式更灵活,所有信号机制全部自定义,waitpid信号通知由系统决定,开发最简单,两种并没有特别大的差别,
比如php 的fpm子进程管理用的是waitpid,nginx的模式是信号管理,这两个都在实际场景中得到了大量的应用。

原文出处:http://www.imsiren.com/archives/1301