等待perl的子进程

时间:2013-09-11 15:44:56

标签: perl unix posix fork ipc

我遇到了捕获子进程返回状态的问题。下面是我的代码的简化版本。

use Modern::Perl;
use POSIX;
use AnyEvent;

my @jobs = (1, 7, 3, 9 , 4 , 2);
my %pid;
my %running;

my $t = AE::timer 0, 5, sub{
    while(scalar( keys %running < 3) && scalar (@jobs)){
        my $job = shift @jobs;
        $running{$job}=1;
        $pid{$job} = run($job);
    }
    for(keys %running){
        delete $running{$_} unless check($pid{$_},$_);
    }
    exit unless scalar keys %running;
};

AnyEvent->condvar->recv;

sub func_to_run{
    my $id = shift;
    close STDOUT;
    open STDOUT, ">>$id.log";
    exec '/bin/sleep', $id;
}

sub run{
    my $id = shift;
    print "starting job $id\n";
    my $pid = fork();
    return $pid if $pid;
    func_to_run($id);
}

sub check{
    my ($pid,$id) = @_;
    my $result = waitpid($pid, WNOHANG);
    {
        if ($result == $pid) {
            my $rc = $? >> 8;
            print "Job $id finished with code $rc\n";
            return 0;
        }
        elsif ($result == -1 and $! == ECHILD) {
            print "Job $id finished running, not sure if it was sucessfull\n";
            return 0;
        }
        elsif ($result == 0) {
            return 1;
        }
        redo;
    }
}

输出:

starting job 1
starting job 7
starting job 3
Job 1 finished running, not sure if it was sucessfull
Job 3 finished running, not sure if it was sucessfull
starting job 9
starting job 4
Job 7 finished running, not sure if it was sucessfull
starting job 2
Job 4 finished running, not sure if it was sucessfull
Job 9 finished running, not sure if it was sucessfull
Job 2 finished running, not sure if it was sucessfull

为什么waitpid()返回-1而不是返回状态?

编辑: 我将system + exit更改为exec。这就是我最初做的事情。我的目标是能够发出子进程的信号,我实际上并不认为这可以用系统完成。

kill($pid,'HUP');

编辑2:     可以同时运行多个子进程,这是从AE :: timer模块调用的。我想知道的是为什么我从waitpid()得到-1,这表明孩子被收获了。

编辑3:我已经使用输出

将代码更改为完整的工作示例

1 个答案:

答案 0 :(得分:2)

我检查了你的代码在linux上用strace命令实际做了些什么。以下是您看到的sleep命令之一:

$ strace -f perl test.pl
...
[pid  4891] nanosleep({1, 0}, NULL)     = 0
[pid  4891] close(1)                    = 0
[pid  4891] close(2)                    = 0
[pid  4891] exit_group(0)               = ?
[pid  4891] +++ exited with 0 +++
 2061530, 64, 4990) = -1 EINTR (Interrupted system call)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=4891, si_status=0, si_utime=0, si_stime=0} ---
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
rt_sigreturn()                          = -1 EINTR (Interrupted system call)
clock_gettime(CLOCK_MONOTONIC, {97657, 317300660}) = 0
clock_gettime(CLOCK_MONOTONIC, {97657, 317371410}) = 0
epoll_wait(3, {{EPOLLIN, {u32=4, u64=4294967300}}}, 64, 3987) = 1
clock_gettime(CLOCK_MONOTONIC, {97657, 317493076}) = 0
read(4, "\1\0\0\0\0\0\0\0", 8)          = 8
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED|WCONTINUED, NULL) = 4891
wait4(-1, 0x7fff8f7bc42c, WNOHANG|WSTOPPED|WCONTINUED, NULL) = -1 ECHILD (No child processes)
clock_gettime(CLOCK_MONOTONIC, {97657, 317738921}) = 0
epoll_wait(3, {}, 64, 3986)             = 0
clock_gettime(CLOCK_MONOTONIC, {97661, 304667812}) = 0
clock_gettime(CLOCK_MONOTONIC, {97661, 304719985}) = 0
epoll_wait(3, {}, 64, 1)                = 0
...

[pid 4891]开始的行来自sleep命令,其余来自您的脚本。您可以看到脚本正在调用wait4()系统调用并返回睡眠过程的PID - 可能是脚本正在使用的事件循环的一部分。这就是为什么你从waitpid()的电话中得到-1的原因 - 已经收到了子进程。

顺便说一句,AnyEvent文档有一个关于观察子进程并检查其返回代码的部分(CHILD PROCESS WATCHERS)。来自文档:

my $done = AnyEvent->condvar;

my $pid = fork or exit 5;

my $w = AnyEvent->child (
   pid => $pid,
   cb  => sub {
      my ($pid, $status) = @_;
       warn "pid $pid exited with status $status";
      $done->send;
   },
);

# do something else, then wait for process exit
$done->recv;

关于使用system()exec()生成流程,使用exec()是正确的。这是因为system()创建了一个子进程来执行其命令,而exec()用命令替换当前进程。这意味着$pid中的system()将引用分叉的Perl脚本,而不是Perl脚本运行的命令。