2009-05-26 9 views
10

mam jakiś kod Perl, który wykonuje skrypt do wielu parametrów, w celu uproszczenia, będę po prostu założyć, że mam kodu, który wygląda tak:Jak sprawić, by Perl czekał, aż procesy potomne rozpoczną się w tle z systemem()?

for $p (@a){ 
    system("/path/to/file.sh $p&"); 
} 

Chciałbym zrobić kilka innych rzeczy po to, ale nie mogę znaleźć sposobu na poczekanie na zakończenie wszystkich procesów potomnych.

Konwersja kodu na fork() byłaby trudna. Czy nie ma łatwiejszego sposobu?

+0

Czy potrzebny jest rzeczywisty kod powrotu z pliku .sh? Czy możesz zmienić skrypt tak, aby po zakończeniu był zapisywany do pliku? – mkb

+1

Dlaczego konwersja kodu na widelec byłaby trudna? Ukryj wszystkie szczegóły w podprogramie. Można nawet przedefiniować system(), aby zamiast tego wywołać nowy podprogram. –

+0

Prawdopodobnie po prostu podzielę tę część na osobny skrypt, który wykonuje rozwidlenie, i zadzwonię do tego z wywołania systemowego ... –

Odpowiedz

16

Korzystanie widelec/exec/wait nie jest takie złe:

my @a = (1, 2, 3); 
for my $p (@a) { 
    my $pid = fork(); 
    if ($pid == -1) { 
     die; 
    } elsif ($pid == 0) { 
     exec '/bin/sleep', $p or die; 
    } 
} 
while (wait() != -1) {} 
print "Done\n"; 
+1

Nie sprawdzasz uszkodzonego widelca i naciskasz go na @pids, a nie $ pid. Wyjście prawdopodobnie powinno być kostką, jeśli to wykona, oznacza to, że exec nie działa. –

+0

Naprawiono. Dzięki, nie jestem native speakerem Perla. – Dave

+0

$ pid == -1 zamiast tego prawdopodobnie powinno być! Defined ($ pid). –

8

Konwersja do trybu fork() może być trudna, ale jest poprawnym narzędziem. system() jest wywołaniem blokującym; otrzymujesz zachowanie nieblokujące, wykonując powłokę i nakazując jej uruchomienie skryptów w tle. Oznacza to, że Perl nie ma pojęcia, jakie mogą być PIDy dzieci, co oznacza, że ​​twój skrypt nie wie, na co czekać.

Możesz spróbować przekazać PID do skryptu Perla, ale to szybko wymyka się spod kontroli. Użyj fork().

13

Będziesz musiał coś zmienić, zmiana kodu na używanie widelca jest prawdopodobnie prostsza, ale jeśli jesteś martwy nastawiony na używanie widelca, możesz użyć skryptu powłoki opakowania, który dotknie plik, gdy zostanie zrobiony, a następnie poproś swój kod Perla o potwierdzenie istnienia plików.

Oto wrapper:

#!/bin/bash 

$* 

touch /tmp/$2.$PPID 

Kod Perl wyglądałby następująco:

for my $p (@a){ 
    system("/path/to/wrapper.sh /path/to/file.sh $p &"); 
} 
while (@a) { 
    delete $a[0] if -f "/tmp/$a[0].$$"; 
} 

Ale myślę, że kod rozwidlania jest bezpieczniejsze i bardziej przejrzyste:

my @pids; 
for my $p (@a) { 
    die "could not fork" unless defined(my $pid = fork);\ 
    unless ($pid) { #child execs 
     exec "/path/to/file.sh", $p; 
     die "exec of file.sh failed"; 
    } 
    push @pids, $pid; #parent stores children's pids 
} 

#wait for all children to finish 
for my $pid (@pids) { 
    waitpid $pid, 0; 
} 
+0

To jest o wiele lepsza odpowiedź. – zdim

+0

Wygląda na to, że w ten sposób powstaje wiele procesów zombie. https://en.wikipedia.org/wiki/Zombie_process –

+1

@PratyushRathore Nie powinno. Zombie wyniki, gdy proces macierzysty nie wywołuje oczekiwania lub waitpid. Jak widać, rodzic wywołuje waitpid dla dziecka, które się zakończyło. Jeśli dostajesz gromadę zombie, to albo robisz coś źle, albo jedno z pierwszych dzieci zabiera dużo czasu, a później dzieci wyjechały (ale nie zostały zebrane). Możesz przełączyć na 'while (waitpid (-1, WNOHANG)> 0) {sleep 1}', aby wykonać następne wychodzące dziecko. –