2012-06-06 16 views
10

Ho uno script che genera un gruppo di figli. Il genitore deve aspettare che tutti i bambini finiscano.Elaborazione di processi figlio da Perl

mio script esegue simile al seguente script Perl:

#! /usr/bin/perl 
use strict; 
use warnings; 

print "I am the only process.\n"; 

my @children_pids; 

for my $count (1..10){ 
     my $child_pid = fork(); 
     if ($child_pid) { # If I have a child PID, then I must be the parent 
       push @children_pids, $child_pid; 
     } 
     else { # I am the child 
       my $wait_time = int(rand(30)); 
       sleep $wait_time; 
       my $localtime = localtime; 
       print "Child: Some child exited at $localtime\n"; 
       exit 0; # Exit the child 
     } 
} 

foreach my $child (@children_pids) { 
     print "Parent: Waiting on $child\n"; 
     waitpid($child, 0); 
     my $localtime = localtime; 
     print "Parent: Child $child was reaped - $localtime.\n"; 
} 

print "All done.\n"; 

simile al codice ho fornito in precedenza, ogni bambino può richiedere un tempo differente per terminare.

Il problema si verifica quando provo a raccogliere i bambini eseguendo il looping dei PID figli, in quest'ultimo blocco foreach, il genitore attende i bambini nell'ordine in cui sono stati creati.

Ovviamente i bambini non finire nell'ordine cui sono generati e così mi sono lasciato con un gruppo di processi zombie per i bambini che capita di finire presto.

Nel mio codice attuale, questi bambini possono finire giorni prima l'uno dell'altro e il numero di processi di zombi che fluttuano intorno può crescere a centinaia.

C'è un modo migliore per raccogliere un gruppo di bambini?

risposta

12

Se il processo genitore non ha bisogno di essere a conoscenza dello stato di completamento dei suoi figli allora si può solo impostare

$SIG{CHLD} = 'IGNORE'; 

che raccoglieranno automaticamente tutti i bambini in quanto completi.

Se fai necessità di essere informato dei bambini completamento, quindi il gestore di segnale deve essere impostato per raccogliere tutti i possibili processi

use POSIX(); 

$SIG{CHLD} = sub { 
    while() { 
    my $child = waitpid -1, POSIX::WNOHANG; 
    last if $child <= 0; 
    my $localtime = localtime; 
    print "Parent: Child $child was reaped - $localtime.\n"; 
    } 
}; 
+1

Motivo principale per essere informati: per vedere se hanno avuto successo. – ikegami

+0

Hey Borodin, controlla [Syntax :: Feature :: Loop] (http://search.cpan.org/perldoc?Syntax::Feature::Loop) – ikegami

+0

@ikegami: sì, a volte lo uso. Per lo più faccio il flit tra l'uso di 'while (1)', 'while()' e '{... redo; } '. Nessuno è veramente soddisfacente. – Borodin

6

utilizzare "-1" per il pid o utilizzare la funzione wait() in modo da attendere qualsiasi processo secondario. Viene restituito il pid raccolto, quindi è possibile controllarlo contro l'elenco, se necessario. Se ciò non è accettabile, periodicamente waitpid per ogni pid nell'elenco con POSIX :: WNOHANG() come secondo argomento.

5

Borodin's answer è perfettamente bene per la mietitura asincrona dei bambini in quanto terminare.

Se, come la tua domanda e il codice suggeriscono a me, che desideri per il sincrona (blocco) raccogliendo di tutti i bambini in sospeso nell'ordine in cui essi terminano, il genitore può semplicemente fare questo:

use feature qw(say); 

... 

# Block until all children are finished 
while (1) { 
    my $child = waitpid(-1, 0); 
    last if $child == -1;  # No more outstanding children 

    say "Parent: Child $child was reaped - ", scalar localtime, "."; 
} 

say "All done." 
1

Mai utilizzare un ciclo come questo per attendere per i bambini:

while (1) { 
    my $child = waitpid(-1, POSIX::WNOHANG); 
    last if $child == -1; 
    print "Parent: Child $child was reaped\n"; 
} 

Il processo padre consumerà 100% della CPU in attesa che i processi figli a morire - e specialmente quando possono correre per molto tempo. Almeno aggiungere un sonno (pessima idea - quando muoiono rapidamente, il genitore è in attesa).

Utilizzare sempre un blocco di attesa + contare TERM/INT/ppid per simpatia !:

my $loop = 1; 
$SIG{CHLD} = 'DEFAULT'; # turn off auto reaper 
$SIG{INT} = $SIG{TERM} = sub {$loop = 0; kill -15 => @children_pids}; 
while ($loop && getppid() != 1) { 
    my $child = waitpid(-1, 0); 
    last if $child == -1; 
    print "Parent: Child $child was reaped\n"; 
} 

Questo blocco aspettare che ovviamente non è possibile quando il processo padre ha anche a che fare altre cose - come il getppid() chiama ;-).Per questo, puoi usare un socketpair() e metterlo in un select() che fa una chiamata bloccante. Anche il controllo del ciclo può trarre vantaggio da questo.

+0

Non dovrebbe durare per sempre, è quello che fa la linea 'last if'. Se nessun bambino è morto, esce dal ciclo. –

+0

Non si tratta del ciclo continuo, si tratta dei cicli di CPU 100% utilizzati durante il ciclo. Se occorrono 10 minuti per terminare 200 processi, è 1 processo che spreca il 100% dei cicli cpu per 10 minuti, mentre se si blocca, questo è praticamente nessuno. – CowboyTim