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

Posted sesiria

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于信号类程序的同步机制相关的知识,希望对你有一定的参考价值。

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

基于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);

以上是关于关于信号类程序的同步机制的主要内容,如果未能解决你的问题,请参考以下文章

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

关于QT信号和槽机制的重复绑定错误及改正

操作系统| 进程同步详解(主要任务制约关系临界资源临界区同步机制遵循规则信号量机制信号量的应用)

操作系统(二 )| 进程(概念特征状态进程控制进程同步机制信号量应用同步机制遵循规则程序执行特点线程引入目的与进程的区别)

进程同步详解(进程同步机制遵循规则信号量机制信号量的应用,经典进程同步问题)

Java并发工具类Semaphore