关于信号类程序的同步机制
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);
}
以上是关于关于信号类程序的同步机制的主要内容,如果未能解决你的问题,请参考以下文章
如何从另一个线程的 cpp 代码同步调用 qml 信号处理程序?