11

我有一个产生一组孩子的脚本。父母必须等待每个孩子完成。

我的脚本执行类似于以下 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";

与我上面提供的代码类似,每个孩子可能需要不同的时间来完成。

问题是当我尝试通过遍历子 PID 来获取子时,在最后一个foreach块中,父级按照子级的创建顺序等待子级。

显然,孩子们并没有按照他们产生的顺序完成,所以我留下了一堆僵尸进程,这些孩子碰巧提前完成了。

在我的实际代码中,这些孩子可能会提前几天完成,并且漂浮的僵尸进程的数量可能会增长到数百个。

有没有更好的方法让我收获一组孩子?

4

4 回答 4

13

如果您的父进程不需要知道其子进程的完成状态,那么您可以设置

$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";
  }
};
于 2012-06-06T23:18:53.293 回答
6

对 pid 使用“-1”,或使用 wait() 函数以便等待任何子进程。获得的 pid 被返回,因此您可以在必要时根据您的列表检查它。如果这是不可接受的,则使用 POSIX::WNOHANG() 作为第二个参数定期等待列表中的每个 pid。

于 2012-06-06T23:16:46.717 回答
5

Borodin 的回答对于孩子们终止时的异步收割非常好。

如果,正如您的问题和代码向我建议的那样,您正在寻找所有未完成子节点的同步阻塞)收获,则父节点可以简单地执行以下操作:

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."
于 2012-06-07T04:52:07.207 回答
1

永远不要使用这样的循环来等待孩子:

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

父进程在等待子进程死亡时会消耗 100% 的 cpu——尤其是当它们可以运行很长时间时。至少添加一个睡眠(坏主意 - 当他们快速死去时,父母正在等待)。

总是对 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() 中。甚至循环检查也可以从中受益。

于 2012-06-09T09:52:54.373 回答