Evitare le condizioni di gara fork () / SIGCHLD
Domanda
Ti preghiamo di considerare il seguente pseudo-codice 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);
}
}
Nell'esempio sopra c'è una condizione di gara. È possibile per " / * child stuff * /
" per finire prima di " / * parent stuff * /
" inizia il che può comportare l'aggiunta del pid di un bambino all'elenco dei bambini dopo la sua uscita e la sua rimozione. Quando arriverà il momento in cui l'app si chiuderà, il genitore attenderà all'infinito il completamento del figlio già finito.
Una soluzione che mi viene in mente per contrastare questo è quella di avere due elenchi: start_children
e finito_children
. Aggiungerei a start_children
nello stesso posto in cui sto aggiungendo children
ora. Ma nel gestore del segnale, invece di rimuovere da children
avrei aggiunto a finito_children
. Quando l'app viene chiusa, il genitore può semplicemente attendere fino a quando la differenza tra start_children
e finito_children
è zero.
Un'altra possibile soluzione che mi viene in mente è l'utilizzo della memoria condivisa, ad es. condividere l'elenco dei figli dei genitori e lasciare che i bambini .add
e .remove
stessi? Ma non ne so molto di questo.
EDIT: Un'altra possibile soluzione, che è stata la prima cosa che mi è venuta in mente, è semplicemente aggiungere un sleep (1)
all'inizio di / * child stuff * / ma questo ha un odore strano per me, motivo per cui l'ho lasciato fuori. Non sono nemmeno sicuro che sia una soluzione al 100%.
Quindi, come correggeresti questa condizione di gara? E se esiste un modello raccomandato consolidato, per favore fatemi sapere!
Grazie.
Soluzione
La soluzione più semplice sarebbe quella di bloccare il segnale SIGCHLD prima di fork ()
con sigprocmask ()
e sbloccarlo nel codice genitore dopo aver elaborato il pid.
Se il bambino è morto, il gestore del segnale per SIGCHLD verrà chiamato dopo aver sbloccato il segnale. È un concetto di sezione critica: nel tuo caso la sezione critica inizia prima di fork ()
e termina dopo children.add ()
.
Altri suggerimenti
Se non puoi usare un frammento critico, forse un semplice contatore può fare questo lavoro. +1 quando aggiungi, -1 quando rimuovi, non importa quale succede prima, alla fine puoi ottenere zero quando tutto è finito.
Oltre all'esistente "figlio" aggiungere una nuova struttura di dati "morti precoci". Ciò manterrà pulito il contenuto dei bambini.
// 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: questo può essere semplificato se il processo è a thread singolo - earlyDeaths non deve essere un contenitore, deve solo contenere un pid.
Forse un algoritmo ottimista? Prova children.remove (pid) e, se fallisce, vai avanti con la vita.
O controlla che pid sia presente nei bambini prima di provare a rimuoverlo?