【问题标题】:How can I make Perl wait for child processes started in the background with system()?如何让 Perl 等待使用 system() 在后台启动的子进程?
【发布时间】:2010-10-29 00:46:00
【问题描述】:

我有一些 Perl 代码为多个参数执行 shell 脚本,为了简化,我假设我的代码如下所示:

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

在那之后我想做更多的事情,但我找不到等待所有子进程完成后再继续的方法。

将代码转换为使用 fork() 会很困难。没有更简单的方法吗?

【问题讨论】:

  • 您需要 file.sh 的实际返回码吗?您可以更改脚本以便在完成时写入文件吗?
  • 为什么将代码转换为 fork 会很困难?隐藏子程序中的所有细节。你甚至可以重新定义 system() 来调用你的新子程序。
  • 我可能只是将此部分拆分为一个单独的脚本来执行分叉,并从系统调用中调用它...

标签: linux perl wait child-process


【解决方案1】:

转换为 fork() 可能很困难,但它是正确的工具。 system() 是一个阻塞调用;你通过执行一个shell并告诉它在后台运行你的脚本来获得非阻塞行为。这意味着 Perl 不知道孩子的 PID 可能是什么,这意味着您的脚本不知道要等待什么。

您可以尝试将 PID 传递给 Perl 脚本,但这很快就会失控。使用 fork()。

【讨论】:

    【解决方案2】:

    您将不得不更改一些内容,更改代码以使用 fork 可能更简单,但如果您坚决反对使用 fork,您可以使用一个包装器 shell 脚本,该脚本在完成时会触及文件,然后您的 Perl 代码检查文件是否存在。

    这是包装器:

    #!/bin/bash
    
    $*
    
    touch /tmp/$2.$PPID
    

    您的 Perl 代码如下所示:

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

    但我认为分叉代码更安全、更清晰:

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

    【讨论】:

    • 这是一个更好的答案。
    • 显然这样做会产生很多僵尸进程。 en.wikipedia.org/wiki/Zombie_process
    • @PratyushRathore 它不应该。当父进程没有调用 wait 或 waitpid 时会产生僵尸。如您所见,父母正在为已退出的孩子调用 waitpid。如果你得到一堆僵尸,那么要么你做错了什么,要么第一个孩子花了很长时间,后来的孩子已经退出(但没有被收割)。你可以切换到while (waitpid(-1, WNOHANG) > 0) { sleep 1 } 来收获下一个退出的孩子。
    • "第一个孩子用了很长时间,后来的孩子已经退出(但没有被收割)。"是的,这正是我的情况。显然,这是一个非常非常特殊的情况,有很多流程。我认为,我以非常糟糕的风格写了最后一条评论。我只是想确保人们在执行此操作时具有僵尸进程的上下文。
    【解决方案3】:

    使用 fork/exec/wait 还不错:

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

    【讨论】:

    • 您不是在检查失败的分叉,而是将裸词 pid 推送到 @pids,而不是 $pid。退出可能应该是一个死,如果它执行意味着执行失败。
    • 已修复。谢谢,我不是 Perl 的母语人士。
    • $pid == -1 应该是 !defined($pid) 。
    猜你喜欢
    • 2011-05-24
    • 2011-03-25
    • 1970-01-01
    • 1970-01-01
    • 2014-11-08
    • 1970-01-01
    • 2013-12-31
    • 2013-09-15
    • 1970-01-01
    相关资源
    最近更新 更多