从Perl中获取子进程

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个回答

14

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

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

1
主要原因是为了知道他们是否成功。 - ikegami
嘿,Borodin,看看Syntax::Feature::Loop - ikegami
@ikegami:是的,有时我会使用那个。我大多数时间都在使用while (1)while (){... redo;}之间切换。没有一个真正令人满意。 - Borodin
1
C语言的for循环在我看来一直是件丑陋的事情。如果没有C语言的先前了解,它是无法理解的,也很少能做到你真正想要的。它有一个优点:continue子句位于循环顶部,这是它应该存在的位置。 - Borodin
为什么会使用waitpid的非阻塞版本?我在这里和perlipc文档中都看到了它,但没有描述为什么。在这种情况下,既然我刚刚收到了一个子进程的死亡消息,waitpid不是保证返回一些东西吗?而且,那个while()循环对CPU来说似乎是一个相当不必要的负担... 我错过了什么吗? - zrajm
显示剩余3条评论

6

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


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

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()中。即使循环检查也可以从中受益。


不应该无限循环,这就是“last if”行的作用。如果没有子进程死亡,它会退出循环。 - Nigel Horne
这不是关于永久循环的问题,而是关于在循环期间使用了100%的CPU周期。如果需要10分钟才能结束200个进程,则1个进程会浪费100%的CPU周期10分钟,而如果您阻止,则基本上没有浪费。 - CowboyTim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接