Получение дочерних процессов из Perl

У меня есть script, который порождает набор детей. Родитель должен ждать завершения каждого из них.

My script выполняет аналогично следующему perl script:

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

Как и в приведенном выше коде, каждый ребенок может занять другое время.

Проблема заключается в том, что когда я пытаюсь пожинать детей, перебирая дочерние PID, в этом последнем блоке foreach, родитель ждет детей в том порядке, в котором они созданы.

Очевидно, что дети не заканчиваются в порядке, в котором они рождаются, и поэтому я остаюсь с кучей процессов зомби для детей, которые заканчиваются раньше.

В моем фактическом коде эти дети могут заканчивать дни друг перед другом, а число процессов зомби, плавающих вокруг, может вырасти в сотнях.

Есть ли лучший способ получить набор детей?

Ответ 1

Если ваш родительский процесс не должен знать о статусе завершения своих детей, вы можете просто установить

$SIG{CHLD} = 'IGNORE';

который автоматически пожнет всех детей по мере их завершения.

Если вам нужно получить информацию о завершении работы с детьми, тогда обработчик сигнала должен быть настроен на использование всех возможных процессов.

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

Ответ 2

используйте "-1" для pid или используйте функцию wait(), чтобы вы ожидали какого-либо дочернего процесса. Возвращается pid, поэтому вы можете проверить его в своем списке, если это необходимо. Если это неприемлемо, то периодически подождите waitpid для каждого pid в вашем списке с помощью POSIX:: WNOHANG() в качестве второго аргумента.

Ответ 3

Ответ Бородина отлично подходит для асинхронного пожинания детей по мере их завершения.

Если, как мне кажется ваш вопрос и код, вы ищете синхронную (блокирующую) уборку всех выдающихся детей в том порядке, в котором они заканчиваются, родительский может просто сделать это:

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."

Ответ 4

Никогда не используйте такой цикл, чтобы ждать детей:

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

Родительский процесс будет потреблять 100% процессор, ожидая, пока дочерние процессы умрут, особенно когда они могут работать в течение длительного времени. По крайней мере, добавьте сон (плохая идея - когда они умирают быстро, родитель ждет).

Всегда используйте счетчик ожидания + счетчик для TERM/INT/ppid для удобства!:

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

Эта блокировка ждет, конечно, не возможно, когда родительский процесс также должен делать другие вещи - например, вызов getppid();-). Для этого вы можете использовать socketpair() и поместить его в select(), который выполняет блокирующий вызов. Даже проверка цикла может выиграть от этого.