AnyEvent::Fork 如何等待子进程

3
我正在查看AnyEvent::Fork模块。我有20个外部脚本需要并行调用(每次6个),并在所有脚本执行完毕后总结它们的输出。我不知道如何实现。
这个模块中的示例代码(只调用一个子进程)存在问题。我只是向代码添加了一个简单的睡眠功能,以免立即返回,但父进程立即退出而不等待子进程。
open my $output, ">/tmp/log" or die "$!";

AnyEvent::Fork
   ->new
   ->eval ('
        # compile a helper function for later use
        sub run {
           my ($fh, $output, @cmd) = @_;

           # perl will clear close-on-exec on STDOUT/STDERR
           open STDOUT, ">&", $output or die;
           open STDERR, ">&", $fh or die;

           ### Added by me to demonstrate that
           ### $cv->recv returns immediately.
           sleep 5;

           exec @cmd;
        }
     ')
   ->send_fh ($output)
   ->send_arg ("/bin/echo", "hi")
   ->run ("run", my $cv = AE::cv);

my $stderr = $cv->recv;

结果是 /tmp/log 是空的。我不明白如何在这里使用 condvar,文档中也没有相关说明。是否可以使用 condvar 获取正在运行的子进程数量?请帮忙解决这个问题。 更新:主要问题在于父进程没有等待子进程完成。

我可以复制这个问题,但是没有明显的解决方案。我没有时间进一步查看。返回值为 $stderr 的事实很有趣。也许它并不表示进程的结束。 - ikegami
这是与AnyEvent->condvar不同的API吗?在运行您的代码后,我发现在5秒后“hi”出现在/tmp/log中。您对测试脚本的预期行为是什么? - G. Cito
@G.Cito 我这边 /tmp/log 是空的。ikegami 已经确认了。 - cstamas
已经安装了5.20.1版本并尝试了一切,结果都与上述一致。由于@ikegami确认了相同的行为,我能想到的可能解释它的明显错误(Unix和权限?)不适用。这将得到解决。 - G. Cito
@cstamas,你说程序没有等待子进程完成,我已经确认了。如果您等待5秒钟,“/tmp/log”不会为空。 - ikegami
显示剩余5条评论
1个回答

4
这里的问题是父进程和子进程是独立的,可以彼此独立运行,因此如果一个需要等待另一个,则需要显式同步。有很多方法可以做到这一点,最简单的方法是使用AnyEvent::Fork::RPC,并向子进程发送“wait”请求,在完成后回答该请求。
要使用裸露的AnyEvent::Fork进行操作,最简单的方法是利用由->run提供的双向管道。
  AnyEvent::Fork
    ->new
    ->run (sub {
       my ($fh) = @_;
       sysread $fh, my $dummy, 1; # will wait for data, or eof
       ... done, you can now call e.g. $cv->send

sysread函数将尝试从子进程中读取数据。如果子进程从未发送任何内容,这将阻塞父进程直到子进程退出,因为在那时,子进程将关闭管道的一端,而sysread函数会得到一个EOF。

当然,在AnyEvent程序中,您可能不希望阻塞,因此您可以使用I/O watcher:

 ->run (sub {
    my ($fh) = @_;
    my $rw; $rw = AE::io $fh, 0, sub {
       ... read data received, or EOF
       undef $rw;
       ... done, you can now call e.g. $cv->send;
    }
  });

这个技巧同样适用于外部命令,例如在你的例子中使用的 exec 命令,在子进程中清除管道上的 close-on-exec 状态,从而将其传递给 exec 的程序,当继承该管道的所有程序退出时,管道会发出 EOF 信号。

这应该可以让您开始。还有其他方法可以实现,但大多数或甚至所有好的方法都涉及某种管道通信,并且获得其中一个最简单的方法是使用 AnyEvent::Fork 提供的管道。


很棒,谢谢!我已经给了一个+1。我会很快去查看一下。 - cstamas

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