【问题标题】:Reaping child processes from Perl从 Perl 中获取子进程
【发布时间】:2012-06-11 00:26:51
【问题描述】:

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

我的脚本执行类似于以下 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 块中,父级按照子级的创建顺序等待子级。

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

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

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

【问题讨论】:

    标签: perl fork parent-child waitpid


    【解决方案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";
      }
    };
    

    【讨论】:

    • 被告知的主要原因:看看他们是否成功。
    • 嗨,鲍罗丁,看看Syntax::Feature::Loop
    • @ikegami:是的,我有时会使用它。我主要使用while (1)while (){ ... redo; }。没有一个是真正令人满意的。
    • 为什么你给waitpid的第二个参数,FLAGS参数,设置为1?对应的是什么?
    • C for 循环对我来说一直是一件丑陋的事情。如果没有 C 的先验知识,而且很少有您真正想要做的事情,这是无法理解的。它有一个优点:continue 子句位于它所属的循环的顶部。
    【解决方案2】:

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

    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'行的作用。如果没有孩子死亡,则退出循环。
    • 这与永远循环无关,它与循环期间使用的 100% cpu 周期有关。如果200个进程需要10分钟才能结束,那就是1个进程浪费100%cpu周期10分钟,而如果你阻塞,这基本上没有。
    【解决方案3】:

    Borodin's answer 非常适合在孩子终止时异步收割。

    如果,正如您的问题和代码向我建议的那样,您正在寻找同步(阻塞)收割所有未完成的子节点按照它们终止的顺序,父母可以简单地这样做:

    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】:

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

      【讨论】:

        猜你喜欢
        • 2016-02-11
        • 1970-01-01
        • 2011-07-16
        • 2016-11-17
        • 2012-05-21
        • 2013-07-18
        • 2014-12-11
        • 2011-06-24
        • 1970-01-01
        相关资源
        最近更新 更多