Vermeiden einer fork () / SIGCHLD Race-Bedingung
Frage
Bitte beachten Sie die folgenden fork()
/ SIGCHLD
Pseudo-Code.
// 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);
}
}
Im obigen Beispiel gibt es ein Rennen-Zustand. Es ist möglich, für „/* child stuff */
“ vor „/* parent stuff */
“ beginnt zu beenden, die in einem Kind pid wird in der Liste der Kinder zur Folge haben können, nachdem er ausgetreten ist, und nie entfernt werden. Wenn die Zeit für die App kommt zu schließen, wird die Mutter endlos warten, bis das bereits fertiggestellte Kind zu beenden.
Eine Lösung, die ich denken kann, um dem entgegenzuwirken ist zwei Listen haben: started_children
und finished_children
. Ich würde hinzufügen, an der gleichen Stelle started_children
Ich bin jetzt zu children
hinzufügen. Aber in den Signal-Handler anstelle von children
Entfernen Ich würde in finished_children
. Wenn der App schliesst, können die Eltern einfach warten, bis der Unterschied zwischen started_children
und finished_children
Null ist.
Eine andere mögliche Lösung kann ich mir vorstellen ist mit gemeinsam genutztem Speicher, z.B. teilen sich die Liste der Eltern von Kindern und lassen Sie die Kinder .add
und .remove
selbst? Aber ich weiß nicht, zu viel darüber nach.
EDIT: Eine andere mögliche Lösung, die das erste, was war in den Sinn kam, ist einfach eine sleep(1)
zu Beginn der /* child stuff */
hinzufügen, aber das riecht komisch für mich, weshalb ich es weggelassen. Ich bin auch nicht sicher, es ist ein 100% fix.
So, wie würden Sie korrigieren dieses Rennen-Zustand? Und wenn es ein gut etabliertes empfohlenen Muster für diese, lassen Sie es mich wissen!
Danke.
Lösung
Einfachste Lösung wäre SIGCHLD Signal vor fork()
mit sigprocmask()
zu blockieren und es in übergeordnetem Code zu entsperren, nachdem Sie die pid bearbeitet haben.
Wenn das Kind starb, Signal-Handler für SIGCHLD wird aufgerufen, nachdem Sie das Signal zu entsperren. Es ist ein kritischer Abschnitt Konzept -. In Ihrem Fall kritische Abschnitt beginnt, bevor fork()
und endet nach children.add()
Andere Tipps
Wenn Sie nicht kritisches Fragment verwenden kann, vielleicht ein einfacher Zähler kann diesen Job machen. +1, wenn add, -1, wenn entfernen, kein mater, die man zuerst passieren, man kann schließlich Null erhalten, wenn alles getan ist.
Zusätzlich zu den bestehenden „Kindern“ fügen Sie eine neue Datenstruktur „frühe Todesfälle“. Dies hält den Inhalt der Kinder sauber.
// 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);
}
}
}
EDIT:. Dies vereinfacht werden kann, wenn der Prozess Single-Threaded ist - earlyDeaths nicht über einen Behälter sein, es hat nur ein pid halten
Vielleicht ein optimistischer Algorithmus? Versuchen children.remove (pid), und wenn es fehlschlägt, fahren Sie mit dem Leben.
oder prüfen, ob pid bei Kindern ist, bevor Sie versuchen, es zu entfernen?