【问题标题】:Avoiding a fork()/SIGCHLD race condition避免 fork()/SIGCHLD 竞争条件
【发布时间】:2010-09-25 07:30:51
【问题描述】:

请考虑以下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_childrenfinished_children 之间的差异为零。

我能想到的另一个可能的解决方案是使用共享内存,例如分享家长的孩子名单,让孩子.add.remove自己?但我对这方面了解不多。

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

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

谢谢。

【问题讨论】:

  • 只是我还是那个信号处理程序不是异步安全的?当新的 SIGCHLD 在中间中断时,children.remove() 怎么可能实现不爆炸?

标签: c fork


【解决方案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。

【讨论】:

  • 它并不能真正解决竞争条件 - 当父母在 if (!earlyDeaths.contains(pid))children.add(pid) 之间时,孩子可能会死去
【解决方案2】:

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

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

【讨论】:

  • 我喜欢这个解决方案。不幸的是,我在 PHP 中执行此操作,并且版本中还没有 sigprocmask() :( 虽然它在 CVS 中,所以我想这只是时间问题。感谢您提供的信息。也许我应该为此使用不同的语言项目——似乎在 PHP 中也没有类似的 setproctitle()。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    或者在尝试删除之前检查 pid 是否存在于子项中?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-30
      • 2010-09-25
      • 2019-06-12
      • 1970-01-01
      • 2020-01-16
      • 2014-04-02
      • 2017-10-04
      相关资源
      最近更新 更多