Éviter les conditions de concurrence fork () / SIGCHLD
Question
Veuillez considérer le pseudo-code fork ()
/ SIGCHLD
suivant.
// 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. C'est possible pour " / * enfant * /
" terminer avant "" / * parent stuff * /
" commence, ce qui peut entraîner l’ajout du pid de l’enfant à la liste des enfants après sa sortie, sans jamais l’enlever. Lorsque le moment sera venu de fermer l'application, le parent attendra sans fin que l'enfant déjà terminé soit terminé.
Une solution à laquelle je peux penser pour remédier à cela est d’avoir deux listes: enfants_démarrés
et enfants_finis
. J'ajouterais à started_children
au même endroit que j'ajoute maintenant à enfants
. Mais dans le gestionnaire de signal, au lieu de supprimer de enfants
, je ajouterais à enfants_finis
. À la fermeture de l'application, le parent peut simplement attendre que la différence entre enfants démarrés
et enfants finis
soit égale à zéro.
Une autre solution à laquelle je peux penser consiste à utiliser la mémoire partagée, par exemple. 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 envisageable a été la première chose à faire. Il suffit simplement d’ajouter un sleep (1)
au début de / * enfant * /
mais ça sent drôle pour moi, c'est pourquoi je l'ai laissé de côté. Je ne suis même pas sûr que ce soit une solution à 100%.
Alors, comment corrigeriez-vous cette condition de concurrence? Et s’il existe un modèle bien établi pour cela, faites-le moi savoir!
Merci.
La solution
La solution la plus simple consisterait à bloquer le signal SIGCHLD avant fork ()
avec sigprocmask ()
et à le débloquer dans le code parent après le traitement du pid.
Si un enfant est décédé, le gestionnaire de signal de SIGCHLD sera appelé après le déblocage du signal. C'est un concept de section critique - dans votre cas, la section critique commence avant fork ()
et se termine après children.add ()
.
Autres conseils
Si vous ne pouvez pas utiliser de fragment critique, un simple compteur peut faire ce travail. +1 quand ajouter, -1 quand supprimer, peu importe lequel arrive en premier, vous pouvez éventuellement obtenir zéro quand tout est terminé.
En plus des "enfants" existants ajouter une nouvelle structure de données "décès prématurés". Cela gardera le contenu des enfants propre.
// 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é si votre processus est à thread unique - earlyDeaths ne doit pas nécessairement être un conteneur, il doit simplement contenir un pid.
Peut-être un algorithme optimiste? Essayez children.remove (pid), et si cela échoue, continuez avec la vie.
Ou vérifiez que le pid est chez les enfants avant d'essayer de l'enlever?