Избегание состояния гонки 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 сами? Но я не слишком много знаю об этом.

EDIT: Еще одно возможное решение, которое первым делом пришло в голову, - просто добавить sleep(1) в начале /* child stuff */, но это пахнет мне смешно, и именно поэтому я его оставил. Я даже не уверен, что это 100% -ное исправление.

Итак, как бы вы исправили это состояние гонки? И если у вас есть хорошо зарекомендовавший себя образец, сообщите мне об этом!

Спасибо.

4 ответа

Самое простое решение - заблокировать сигнал 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 у детей, прежде чем пытаться удалить его?

licensed under cc by-sa 3.0 with attribution.