跳转至

同步流以避免讨厌的并发错误

  • 一般而言, 流可能交错的数量与指令的数量呈指数关系
  • 一段错误的代码:
    • 父进程执行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);
    }
}