请考虑以下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_children
和finished_children
。我现在将started_children
添加到我children
添加到children
的同一个地方。但是在信号处理程序中,我没有从finished_children
移除添加到started_children
。当应用关闭时,家长可以等到finished_children
和.add
之间的差异为零。
我能想到的另一个可能的解决方案是使用共享内存,例如:分享父母的孩子名单,并让孩子.remove
和sleep(1)
自己?但我对此并不太了解。
编辑:另一个可能的解决方案,首先想到的是,只需在/* child stuff */
的开头添加一个{{1}},但这对我来说很有趣,这就是我离开的原因出来。我也不确定它是100%修复。
那么,你如何纠正这种竞争条件?如果有一个完善的推荐模式,请告诉我!
感谢。
答案 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是否在儿童中?