2008-12-04 10 views
13

Bitte beachten Sie die folgenden fork()/SIGCHLD Pseudocode.Vermeiden einer Gabel()/SIGCHLD Race Condition

// 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 eine Race-Condition. Es ist möglich, dass "/* child stuff */" beendet wird, bevor "/* parent stuff */" gestartet wird, was dazu führen kann, dass die pid eines Kindes zur Liste der Kinder hinzugefügt wird, nachdem es beendet wurde, und niemals entfernt wird. Wenn es an der Zeit ist, dass die App geschlossen wird, warten die Eltern endlos darauf, dass das bereits fertiggestellte Kind fertig ist.

Eine Lösung, die ich mir vorstellen kann, ist, zwei Listen zu haben: started_children und finished_children. Ich würde started_children an derselben Stelle hinzufügen, die ich zu children jetzt hinzufüge. Aber im Signal-Handler, anstatt children zu entfernen, würde ich zu finished_children hinzufügen. Wenn die App geschlossen wird, kann das übergeordnete Element einfach warten, bis der Unterschied zwischen started_children und finished_children gleich Null ist.

Eine andere mögliche Lösung, die ich mir vorstellen kann, ist Shared-Memory, z. teilen Sie die Liste der Eltern der Eltern und lassen Sie die Kinder .add und .remove sich selbst? Aber ich weiß nicht viel darüber.

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 mir komisch vor, weshalb ich es weggelassen. Ich bin mir auch nicht einmal sicher, ob es eine 100% ige Lösung ist.

Also, wie würdest du diese Rasse-Bedingung korrigieren? Und wenn es dafür ein bewährtes Muster gibt, lassen Sie es mich wissen!

Danke.

+0

Ist es nur ich oder ist das Signal Handler nicht asynch-sicher? Wie könnte children.remove() möglicherweise implementiert werden, um nicht zu explodieren, wenn ein neues SIGCHLD es in der Mitte unterbricht? –

Antwort

15

Einfachste Lösung wäre SIGCHLD Signal vor fork() mit sigprocmask() zu blockieren und im Elterncode freizugeben, nachdem Sie die PID verarbeitet haben.

Wenn Kind untergeht, wird der Signalhandler für SIGCHLD aufgerufen, nachdem Sie das Signal entsperrt haben. Es ist ein kritischer Abschnitt Konzept - in Ihrem Fall beginnt der kritische Abschnitt vor fork() und endet nach children.add().

+0

Ich mag diese Lösung. Leider mache ich das in PHP und es gibt noch kein sigprocmask() in einer Veröffentlichung :(Es ist in CVS obwohl es nur eine Frage der Zeit ist. Danke für die Info. Vielleicht sollte ich eine andere Sprache dafür verwenden project - no setproctitle() - wie in PHP scheint es entweder. –

-1

Zusätzlich zu den vorhandenen "Kinder" fügen Sie eine neue Datenstruktur "frühe Todesfälle" hinzu. Dies hält den Inhalt von Kindern 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 kann vereinfacht werden, wenn der Prozess Single-Threaded ist - earlyDeaths keinen Container sein muss, hat es nur eine pid zu halten.

+0

Es löst nicht wirklich die Race Condition - Kind kann sterben, während der Elternteil zwischen 'if (! EarlyDeaths.contains (pid))' und 'children.add (pid)' – qrdl

0

Wenn Sie kein kritisches Fragment verwenden können, kann ein einfacher Zähler diese Aufgabe übernehmen. +1 wenn add, -1 wenn remove, keine mater, die zuerst passieren, du kannst schließlich Null bekommen, wenn alles erledigt ist.

-1

Vielleicht ein optimistischer Algorithmus? Versuchen Sie children.remove (pid), und wenn es fehlschlägt, fahren Sie mit dem Leben fort.

Oder überprüfen Sie, ob PID in Kindern ist, bevor Sie versuchen, es zu entfernen?