避免fork()/ SIGCHLD竞争条件

时间:2008-12-04 11:36:08

标签: c fork

请考虑以下fork() / SIGCHLD伪代码。

  // main program excerpt
    for (;;) {
      if ( is_time_to_make_babies ) {

        pid = fork();
        if (pid == -1) {
          /* fail */
        } else if (pid == 0) {
          /* child stuff */
          print "child started"
          exit
        } else {
          /* parent stuff */
          print "parent forked new child ", pid
          children.add(pid);
        }

      }
    }

  // SIGCHLD handler
  sigchld_handler(signo) {
    while ( (pid = wait(status, WNOHANG)) > 0 ) {
      print "parent caught SIGCHLD from ", pid
      children.remove(pid);
    }
  }

在上面的例子中,有一个竞争条件。 “/* child stuff */”可能在“/* parent stuff */”开始之前完成,这可能导致孩子的pid在退出后被添加到子列表中,并且永远不会被删除。当应用程序关闭时,父母将无休止地等待已经完成的孩子完成。

我能想到的一个解决方案就是有两个列表:started_childrenfinished_children。我现在将started_children添加到我children添加到children的同一个地方。但是在信号处理程序中,我没有从finished_children移除添加started_children。当应用关闭时,家长可以等到finished_children.add之间的差异为零。

我能想到的另一个可能的解决方案是使用共享内存,例如:分享父母的孩子名单,并让孩子.removesleep(1)自己?但我对此并不太了解。

编辑:另一个可能的解决方案,首先想到的是,只需在/* child stuff */的开头添加一个{{1}},但这对我来说很有趣,这就是我离开的原因出来。我也不确定它是100%修复。

那么,你如何纠正这种竞争条件?如果有一个完善的推荐模式,请告诉我!

感谢。

4 个答案:

答案 0 :(得分:15)

最简单的解决方案是在使用fork() sigprocmask()之前阻止SIGCHLD信号,并在处理完pid后在父代码中取消阻止。

如果孩子死亡,在解锁信号后将调用SIGCHLD的信号处理程序。这是一个关键部分概念 - 在您的情况下,关键部分在fork()之前开始,在children.add()之后结束。

答案 1 :(得分:0)

如果你不能使用关键片段,也许一个简单的计数器可以完成这项工作。添加时为+1,删除后为-1,没有首先发生的情况,完成后最终可以为零。

答案 2 :(得分:-1)

除了现有的“孩子”外,还添加了新的数据结构“早逝”。这将保持儿童的内容清洁。

  // main program excerpt
    for (;;) {
      if ( is_time_to_make_babies ) {

        pid = fork();
        if (pid == -1) {
          /* fail */
        } else if (pid == 0) {
          /* child stuff */
          print "child started"
          exit
        } else {
          /* parent stuff */
          print "parent forked new child ", pid
          if (!earlyDeaths.contains(pid)) {
              children.add(pid);
          } else {
              earlyDeaths.remove(pid);
          }
        }

      }
    }

  // SIGCHLD handler
  sigchld_handler(signo) {
    while ( (pid = wait(status, WNOHANG)) > 0 ) {
      print "parent caught SIGCHLD from ", pid
      if (children.contains(pid)) {
          children.remove(pid);
      } else {
          earlyDeaths.add(pid);
      }
    }
  }

编辑:如果你的进程是单线程的,这可以简化 - earlyDeaths不必是一个容器,它只需要保存一个pid。

答案 3 :(得分:-1)

也许是一种乐观的算法?尝试children.remove(pid),如果失败,继续生活。

或者在尝试删除之前检查pid是否在儿童中?