在 perl 中关闭多个输出管道而不阻塞每个输出管道

Posted

技术标签:

【中文标题】在 perl 中关闭多个输出管道而不阻塞每个输出管道【英文标题】:close multiple output pipes in perl without blocking on each one 【发布时间】:2012-11-11 08:44:06 【问题描述】:

我有一个 perl 脚本,它向多个子进程发送大量输出。我需要能够关闭所有管道的末端,然后等待子流程完成它们的工作。到目前为止,我只成功地关闭了每个管道并等待每个子进程一个一个完成。 更具体地说,我正在做这样的事情:

for ($i=0;$i<24;$i++) 
    my $fh;
    open $fh, "|externalprogram $i";
    $fhs$i=$fh;


#...now I can write output to the pipes
while (moreworktodo()) 
    $whichone, $data = do_some_work();
    print $fhs$whichone $data;

#Now I just need to wait for all the subprocesses to finish.  However, they
#need to do a lot of work that can only begin when they've finished reading input.  So I need to close my end of the pipe to indicate I'm finished.
for ($i=0;$i<24;$i++) 
    my $file = $fhs$i;
    close $file;  #unfortunately, this blocks until process $i finishes
    #meanwhile all the other processes are waiting for EOF 
    #on their STDIN before they can proceed.  So I end up waiting
    #for 24 processes to finish one-at-a-time instead of all at once

让所有子进程迅速完成(关闭它们的标准输入)的一种方法是让我的脚本退出而不关闭(管道)文件句柄,但这并不好,因为脚本是需要的更大工作的一部分在继续之前实际完成子流程的工作。

有什么简单的方法可以关闭每个子进程的标准输入(以便它们都可以完成工作),然后等待所有子进程都完成后再继续?我试过分叉一个子进程来关闭每个管道,但这似乎不起作用——只有父进程的“关闭”实际上关闭了子进程的标准输入并等待子进程完成。

【问题讨论】:

【参考方案1】:

我会自己创建管道而不使用open(P, "|external-program")。 然后就可以关闭管道,不用等待子进程退出。

自己为子进程打开管道的示例:

sub spawn 
  my ($cmd) = @_;

  pipe(my $rp, $wp) or die "pipe failed: $!";

  my $pid = fork();
  die "fork: $!" unless defined($pid);
  if ($pid) 
    # parent
    close($rp);
    return ($wp, $pid);
   else 
    # child
    close($wp);
    open(STDIN, "<&", $rp);
    exec($cmd) or die "exec: $!";
  


sub main 
  $| = 1;
  my ($wp, $pid) = spawn("./child");
  for (1..10) 
    print $wp "sending $_\n";
  
  close($wp);
  print "done\n";
 

 main();

这是一个示例子程序,用于测试 close() 是否正在等待子程序退出:

# file: ./child
while (<STDIN>) 
  print "got: $_";
  sleep(2);

最后一个难题是异步等待子进程退出。 这可以通过$SIGCHLD 处理程序来完成,或者,这里有一个简单的“join_children”函数:

my @child_ids = (1..24); # or whatever ids you want to use
my %pipe;                # hash map from child_id -> pipe handle

sub join_children 
  for my $id (@child_ids) 
    close( $pipe$id );
  

  my $count = scalar(@child_ids);
  while ($count > 0) 
    wait;
    $count--;
  

【讨论】:

以上是关于在 perl 中关闭多个输出管道而不阻塞每个输出管道的主要内容,如果未能解决你的问题,请参考以下文章

如何运行命令行 FFMPEG 并接受多个管道(视频和音频)而不阻塞第一个输入?

如何在导航控制器中关闭视图控制器,而不关闭整个堆栈

如何在 gradle 启动测试中关闭关闭挂钩的输出?

如何在关闭特定选项卡而不是在 Angular 2 中关闭浏览器时清除本地存储?

Xcode 在控制台输出中关闭与自动布局相关的警告

平行管道没有在C中关闭?