避免fork()/ SIGCHLD竞争条件

请考虑以下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 。 我会在我现在添加到children的同一个地方添加到started_children 。 但是在信号处理程序中,我将添加finished_children而不是从children节点中删除。 当应用程序关闭时,父级可以等待,直到started_childrenfinished_children之间的差异为零。

我能想到的另一个可能的解决方案是使用共享内存,例如,共享父项的子项列表,并让子项.add.remove自己? 但我对此并不太了解。

编辑:另一个可能的解决方案,这是我想到的第一件事,就是简单地在/* child stuff */开始添加一个sleep(1) /* child stuff */但这对我来说很有趣,这就是我把它留下来的原因。 我甚至不确定它是100%修复。

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

谢谢。

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

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

如果你不能使用关键片段,也许一个简单的计数器可以完成这项工作。 添加时为+1,删除后为-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。

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

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