同步流以避免讨厌的并发错误
- 一般而言, 流可能交错的数量与指令的数量呈指数关系
- 一段错误的代码:
- 父进程执行
fork
函数, 内核调度新创建的子进程运行, 而不是父进程 - 在父进程能够再次运行之前, 子进程就已经终止, 并且变为了一个僵死进程, 使得内核传递一个
SIGCHLD
信号给父进程 - 当父进程再次变为可运行之前, 内核注意到有未处理的
SIGCHLD
信号, 并通过在父进程中运行处理程序接收这个信号 - 信号处理程序回收终止的子进程, 并且调用
deletejob
(在addjob
之前执行了deletejob
) - 在处理程序运行结束后, 内核运行父进程, 并且调用
addjob
(将不存在的进程添加到了作业列表中)#include <stdio.h> #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){ Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); deletejob(pid); Sigprocmask(SIG_SETMASK, &prev_all, NULL); } if (errno != ECHILD) Sio_error("waitpid error"); errno = olderrno; } int main(){ int pid; sigset_t mask_all, prev_all; Sigfillset(&mask_all); Signal(SIGCHLD, handler); initjobs(); while(1){ if((pid = Fork()) == 0){ Execve("/bin/date", argv, NULL); } Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); addjob(pid); Sigprocmask(SIG_SETMASK, &prev_all, NULL); } exit(0); }
- 父进程执行
[!note] 竞争 这是一个称为竞争的经典同步错误的示例.
main
函数中调用addjob
和处理程序中调用deletejob
之间存在竞争. 如果addjob
赢得资源, 则结果正确, 如果没有, 则发生错误 - 消除竞争的一种方法: - 在fork
之前, 阻塞SIGCHLD
信号, 然后在调用addjob
之后取消阻塞这些信号 - 保证了在子进程被添加到作业列表之后回收该子进程 [!attention] 子进程继承了他们父进程的被阻塞集合, 所以我们必须在调用execve
之前, 小心的接触子进程中阻塞的SIGCHLD
信号 此时, 父进程的SIGCHLD
信号依旧处于阻塞状态#include <stdio.h> #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){ Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); deletejob(pid); Sigprocmask(SIG_SETMASK, &prev_all, NULL); } if (errno != ECHILD) Sio_error("waitpid error"); errno = olderrno; } int main(){ int pid; sigset_t mask_all, mask_one, prev_one; Sigfillset(&mask_all); Sigemptyset(&mask_one); Sigaddset(&mask_one, SIGCHLD); Signal(SIGCHLD, handler); initjobs(); while(1){ Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); if((pid = Fork()) == 0){ Sigprocmask(SIG_SETMASK, &prev_one, NULL); Execve("/bin/date", argv, NULL); } Sigprocmask(SIG_BLOCK, &mask_all, &prev_one); addjob(pid); Sigprocmask(SIG_SETMASK, &prev_one, NULL); } }