14

请考虑以下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.remove他们自己?但我对这方面了解不多。

编辑:另一个可能的解决方案,这是我首先想到的,就是sleep(1)在开头添加一个,/* child stuff */但这对我来说很有趣,这就是我忽略它的原因。我什至不确定这是 100% 修复。

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

谢谢。

4

4 回答 4

16

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

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

于 2008-12-04T12:20:24.597 回答
0

如果您不能使用关键片段,也许一个简单的计数器可以完成这项工作。添加时+1,删除时-1,无论哪一个先发生,完成后你最终可以得到零。

于 2010-06-09T01:29:10.577 回答
-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。

于 2008-12-04T11:46:49.080 回答
-1

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

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

于 2012-04-02T18:21:33.953 回答