2008-12-04 7 views
13

Veuillez prendre en compte les éléments suivants fork()/SIGCHLD pseudo-code.Éviter une fourche()/Conditions de course 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); 
    } 
    } 

Dans l'exemple ci-dessus, il existe une condition de concurrence. Il est possible que "/* child stuff */" se termine avant "/* parent stuff */", ce qui peut entraîner l'ajout du PID d'un enfant à la liste des enfants après sa fermeture et ne jamais être supprimé. Lorsque le moment est venu de fermer l'application, le parent attendra indéfiniment que l'enfant déjà terminé finisse.

Une solution que je peux penser pour contrer cela est d'avoir deux listes: started_children et finished_children. Je voudrais ajouter à started_children dans le même endroit que j'ajoute à children maintenant. Mais dans le gestionnaire de signal, au lieu de supprimer de children je ajouter à finished_children. Lorsque l'application se ferme, le parent peut simplement attendre que la différence entre started_children et finished_children soit nulle.

Une autre solution possible que je peux penser est l'utilisation de la mémoire partagée, par ex. partager la liste des enfants du parent et laisser les enfants .add et .remove eux-mêmes? Mais je ne sais pas trop à ce sujet.

EDIT: Une autre solution possible, qui était la première chose qui me vient à l'esprit, est d'ajouter simplement un sleep(1) au début de /* child stuff */ mais ça me fait drôle, c'est pourquoi je l'ai omis. Je ne suis même pas sûr que ce soit une solution à 100%.

Alors, comment corrigeriez-vous cette condition de course? Et s'il y a un modèle recommandé bien établi pour cela, s'il vous plaît faites le moi savoir!

Merci.

+0

Est-ce que c'est juste moi ou ce gestionnaire de signal n'est-il pas sécurisé? Comment child.remove() pourrait-il être implémenté pour ne pas exploser quand un nouveau SIGCHLD l'interrompt au milieu? –

Répondre

15

La solution la plus simple serait de bloquer le signal SIGCHLD avant fork() avec sigprocmask() et de le débloquer dans le code parent après avoir traité le pid.

Si l'enfant est décédé, le gestionnaire de signal pour SIGCHLD sera appelé après avoir débloqué le signal. C'est un concept de section critique - dans votre cas, la section critique commence avant fork() et se termine après children.add().

+0

J'aime cette solution. Malheureusement, je le fais en PHP et il n'y a pas encore de sigprocmask() dans une version :(Il est dans CVS mais ce n'est qu'une question de temps, merci pour l'info. Peut-être que je devrais utiliser un langage différent pour ça projet - pas de setproctitle() - même en PHP, il semble. –

-1

En plus des "enfants" existants, ajoutez une nouvelle structure de données "décès prématurés". Cela permettra de garder le contenu des enfants propres.

// 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: cela peut être simplifiée si le processus est seul thread - earlyDeaths ne doit pas être un conteneur, il a juste de tenir une pid.

+0

Cela ne résout pas vraiment la condition de race - l'enfant peut mourir alors que le parent est entre 'if (! EarlyDeaths.contains (pid))' et 'children.add (pid)' – qrdl

0

Si vous ne pouvez pas utiliser un fragment critique, un simple compteur peut peut-être effectuer ce travail. +1 lors de l'ajout, -1 lors de la suppression, pas de mater ce qui arrive en premier, vous pouvez finalement obtenir zéro quand tout est fait.

-1

Peut-être un algorithme optimiste? Essayez children.remove (pid), et si cela échoue, passez à la vie.

Ou vérifiez que pid est chez les enfants avant d'essayer de l'enlever?

Questions connexes