Избегание условия гонки 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_children
и finished_children
.Я бы добавил к started_children
в том же месте, которое я добавляю к children
сейчас же.Но в обработчике сигнала, вместо удаления из children
Я бы Добавить Для finished_children
.Когда приложение закрывается, родитель может просто подождать, пока не исчезнет разница между started_children
и finished_children
равен нулю.
Другое возможное решение, о котором я могу подумать, - это использование общей памяти, напримерподелитесь родительским списком дочерних элементов и позвольте дочерним элементам .add
и .remove
сами по себе?Но я не слишком много знаю об этом.
Редактировать:Другое возможное решение, которое было первым, что пришло на ум, - это просто добавить sleep(1)
в начале /* child stuff */
но для меня это странно пахнет, вот почему я опустил это.Я также даже не уверен, что это 100% исправление.
Итак, как бы вы исправили это состояние гонки?И если для этого есть устоявшийся рекомендуемый шаблон, пожалуйста, дайте мне знать!
Спасибо.
Решение
Самым простым решением было бы заблокировать сигнал SIGCHLD перед fork()
с sigprocmask()
и разблокируйте его в родительском коде после того, как вы обработаете 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 находится в дочерних элементах, прежде чем пытаться удалить его?