2012-06-06 8 views
10

Ich habe ein Skript, das eine Reihe von Kindern hervorbringt. Der Elternteil muss warten, bis alle Kinder fertig sind.Ernten untergeordneten Prozesse von Perl

Mein Skript führt die folgende Perl-Skript ähnlich:

#! /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"; 

ähnlich den Code, den ich oben zur Verfügung gestellt habe, jedes Kind eine andere Zeit zu beenden dauern.

Das Problem ist, wenn ich versuche, die Kinder durch Schleifen über die Kinder PIDs zu ernten, in diesem letzten foreach Block, der Eltern wartet auf die Kinder in der Reihenfolge, dass sie erstellt werden.

Offensichtlich enden die Kinder nicht in der Reihenfolge, in der sie hervorgebracht werden, und so bleibt mir ein Haufen Zombie-Prozesse für Kinder, die früher zu Ende gehen.

In meinem tatsächlichen Code können diese Kinder Tage vor einander beenden und die Anzahl der herumschwebenden Zombie-Prozesse kann in den Hunderten wachsen.

Gibt es einen besseren Weg für mich, eine Reihe von Kindern zu ernten?

Antwort

12

Wenn Ihre Eltern-Prozess muss nicht von seinem Kinderabschlussstatus bewusst sein, dann können Sie einstellen, nur

$SIG{CHLD} = 'IGNORE'; 

, die automatisch alle Kinder, wie sie vollständig ernten.

Wenn Sie Notwendigkeit tun von den Kindern Abschluss informiert werden, dann muss das Signal-Handler für die asynchrone Ernte von Kindern ernten alle möglichen Prozesse

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

Hauptgrund, um informiert zu werden: Um zu sehen, ob sie erfolgreich waren. – ikegami

+0

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

+0

@ikegami: ja das benutze ich manchmal. Ich flitze meistens zwischen 'while (1)', 'while()' und '{... redo; } '. Keine sind wirklich zufriedenstellend. – Borodin

6

Verwenden Sie "-1" für die PID, oder verwenden Sie die Funktion wait(), so dass Sie auf einen untergeordneten Prozess warten. Die geerntete PID wird zurückgegeben, sodass Sie sie bei Bedarf mit Ihrer Liste vergleichen können. Wenn das nicht akzeptabel ist, dann warten Sie regelmäßig auf jede PID in Ihrer Liste mit POSIX :: WNOHANG() als zweitem Argument.

5

Borodin's answer ist völlig in Ordnung festgelegt werden, da sie beenden.

Wenn, wie Ihre Frage und Code mir vorschlagen, suchen Sie die synchrone (Blockierung) alle ausstehenden Kinder in der Reihenfolge zu ernten, in der sie beenden, können die Eltern einfach tun:

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

niemals eine Schleife wie diese verwendet für Kinder warten:

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

Der Elternprozess wird zu 100% cpu verbrauchen, während für die Prozesse Kind warten, um zu sterben - e besonders wenn sie lange laufen können. Zumindest einen Schlaf hinzufügen (schlechte Idee - wenn sie schnell sterben, wartet der Elternteil).

immer eine Sperrwarte verwenden + für TERM/INT/ppid für Nettigkeit zählen !:

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"; 
} 

Diese Blockierung ist es natürlich warten nicht möglich, wenn der übergeordnete Prozess auch andere Sachen zu tun hat - wie die getppid() Anruf ;-).Dafür können Sie ein socketpair() verwenden und dieses in eine select() einfügen, die einen blockierenden Aufruf ausführt. Davon kann auch der Loop-Check profitieren.

+0

Es sollte nicht für immer Schleife, das ist, was die 'last if' Zeile tut. Wenn kein Kind gestorben ist, verlässt es die Schleife. –

+0

Es geht nicht um die endlose Schleife, es geht um die 100% CPU-Zyklen, die während der Schleife verwendet werden. Wenn es 10 Minuten dauert, bis 200 Prozesse beendet sind, ist es ein Prozess, der 100% CPU-Zyklen für 10 Minuten verschwendet, während, wenn Sie blockieren, dies im Grunde keine ist. – CowboyTim