关于信号类程序的同步机制

  • Post author:
  • Post category:其他


一、多进程中的信号同步机制:

基于signal interrupt的例子,用于防止race condition

先看以下例子:

#include "csapp.h"

void handler(int sig)
{
    int olderrno = errno;
    sigset_t mask_all, prev_all;
    pid_t pid;

    Sigfillset(&mask_all);
    while((pid = waitpid(-1, NULL, 0)) > 0) {   /* Reap a zombie child */
        Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
        deletejob(pid);                         /* Delete the child from the job list */
        Sigprocmask(SIG_SETMASK, &prev_all, NULL);
    }
    if(errno != ECHILD)
        Sio_error("waitpid error");
    errno = olderrno;
}

int main(int argc, char ** argv)
{
    int pid;
    sigset_t mask_all, prev_all;

    Sigfillset(&mask_all);
    Signal(SIGCHLD, handler);
    initjobs();                                 /* Initialize the job list */

    while(1) {
        if((pid == Fork()) == 0) {              /* Child process */
            Execve("/bin/date", argv, NULL);
        }
        Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);   /* Block SIGCHLD */
        addjob(pid);                            /* Add the child to the job list */
        Sigprocmask(SIG_SETMASK, &prev_all, NULL);  /* Unblock SIGCHLD*/
    }
    exit(0);
}

这个代码存在一定的问题,如果在Fork之后子进程先执行,子进程会先退出并触发父进程的SIGCHLD信号。而此时,父进程还未执行来不及屏蔽信号。也就会导致deletejob 先于addjob先执行。

这就会导致一个race condition而引发问题。

修改后的代码在Fork之前先SIGCHLD屏蔽,然后再执行fork,则可以避免这种race condition问题

修改后的代码如下:

首先屏蔽SIGCHLD信号,然后执行fork

在子进程中先接触对SIGCHLD信号的屏蔽,然后执行execve

在父进程中继续屏蔽所有信号,然后讲子进程的pid加入addjob。

然后再解除所有信号的屏蔽。这样依赖保证了父进程的SIGCHLD的handler一定会在addjob之后才执行。

#include "csapp.h"

void handler(int sig)
{
    int olderrno = errno;
    sigset_t mask_all, prev_all;
    pid_t pid;

    Sigfillset(&mask_all);
    while((pid = waitpid(-1, NULL, 0)) > 0) {   /* Reap a zombie child */
        Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
        deletejob(pid);                         /* Delete the child from the job list */
        Sigprocmask(SIG_SETMASK, &prev_all, NULL);
    }
    if(errno != ECHILD)
        Sio_error("waitpid error");
    errno = olderrno;
}

int main(int argc, char ** argv)
{
    int pid;
    sigset_t mask_all, mask_one, prev_one;

    Sigfillset(&mask_all);
    Sigemptyset(&mask_one);
    Sigaddset(&mask_one, SIGCHLD);
    Signal(SIGCHLD, handler);
    initjobs();                                 /* Initialize the job list */

    while(1) {
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);   /* Block SIGCHLD */
        if((pid == Fork()) == 0) {              /* Child process */
            Sigprocmask(SIG_SETMASK, &prev_one, NULL);  /* Unblock SIGCHLD*/
            Execve("/bin/date", argv, NULL);
        }
        Sigprocmask(SIG_BLOCK, &mask_all, NULL);        /* Parent process */
        addjob(pid);        /* Add the child to the job list */
        Sigprocmask(SIG_SETMASK, &prev_one, NULL);      /* Unblock SIGCHLD */
    }
    exit(0);
}

父进程等待子进程退出的例子:

#include "csapp.h"

volatile sig_atomic_t pid;

void sigchld_handler(int s)
{
    int olderrno = errno;
    pid = waitpid(-1, NULL, 0);
    errno = olderrno;
}

void sigint_handler(int s)
{

}

int main(int argc, char **argv)
{
    sigset_t mask, prev;

    Signal(SIGCHLD, sigchld_handler);
    Signal(SIGINT, sigint_handler);
    Sigemptyset(&mask);
    Sigaddset(&mask, SIGCHLD);

    while(1) {
        Sigprocmask(SIG_BLOCK, &mask, &prev);       /* Block SIGCHLD */
        if(Fork() == 0)                             /* Child*/
            exit(0);
        
        /* Parent */
        pid = 0;
        Sigprocmask(SIG_SETMASK, & prev, NULL);     /* Unblock SIGCHLD */

        /* Wait for SIGCHLD to be received (wasteful) */
        while(!pid)
            ;
        
        /* Do some work after receiving SIGCHLD */
        printf(".");
    }
    exit(0);
}

这段代码存在一些问题。

1)wait(!pid)会反复消耗cpu资源,严重影响程序的性能;

2)如果在其中加上pause(); 如果SIGCHLD在执行pause()之前触发,那么主线程会永远pause()不再被唤醒;

3)如果改成sleep(1);这样也会影响程序的性能;

正确的方法是使用sigsuspend函数

sigsuspend会临时的block当前的进程,直到触发了信号的handler或者进程terminate

他的等效代码是原子级的操作:

sigprocmask(SIG_SETMASK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prevent, NULL);

因此可以使用sigsuspend来执行达到类似的效果:

#include "csapp.h"

volatile sig_atomic_t pid;

void sigchld_handler(int s)
{
    int olderrno = errno;
    pid = Waitpid(-1, NULL, 0);
    errno = olderrno;
}

void sigint_handler(int s)
{

}

int main(int argc, char**argv)
{
    sigset_t mask, prev;

    Signal(SIGCHLD, sigchld_handler);
    Signal(SIGINT, sigint_handler);
    Sigemptyset(&mask);
    Sigaddset(&mask, SIGCHLD);

    while(1) {
        Sigprocmask(SIG_BLOCK, &mask, &prev);       /* Block SIGCHLD */
        if(Fork() == 0)                             /* Child */
            exit(0);
        
        /* Wait for SIGCHLD to be received */
        pid = 0;
        while(!pid)
            sigsuspend(&prev);
        
        /* Optionally unblock SIGCHLD */
        Sigprocmask(SIG_SETMASK, &prev, NULL);

        /* Do some work after receiving SIGCHLD */
        printf(".");
    }
    exit(0);
}



版权声明:本文为sesiria原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。