使用IPC :: open2管道大文件

时间:2012-09-21 14:21:07

标签: multithreading perl

我已经创建了一个包含另一个工具(overlapFeatures)的perl脚本,以便我可以动态地正确转换我的文件格式。我正在处理的文件都是标签分隔表,通常是200万行左右。就其本身而言,overlapFeatures在处理这些问题时没有任何问题。

但是我认为我是通过同时管道这么多线来导致管道锁定的。我知道我需要以某种方式对此进行处理,以便我可以同时读取和写入子进程。但是我真的不明白如何在perl(或任何其他程序)中正确使用线程。据我了解,我可以使用threads甚至IPC::run来解决我的问题。

我的原始脚本最终会导致死锁:

use strict;
use warnings;
use IPC::Open2;

my $infile = shift;
my $featurefile = shift;

my $command = 'overlapFeatures';
my @args = (qw (-a stdin -b), $featurefile);

my ($input, $output);
my $pid = open2($output, $input, $command, @args) 
    or die "Failed with error $!\n";

open (my $infh, '<', $infile) or die "Can't open $infile\n";
while (<$infh>){
    # Do some format conversion...
    chomp
    my @cols = split /\t/;
    # print a modified line to the tool
    print $input join ("\t", @cols[0,2,3,1,5,4]),"\n";
}
close ($input);

while (<$output>){
    # format conversion for ouput
    chomp;
    my @cols = split /\t/;
    print join (",",@cols[0,1,2,5,3,8]),"\n";
}
close ($output);

我尝试重写脚本以按照How to filter a lot of data with IPC::Open2?使用线程,如下所示:

use strict;
use warnings;
use IPC::Open2;
use threads;

my $infile = shift;
my $featurefile = shift;

my $command = 'overlapFeatures';
my @args = (qw (-a stdin -b), $featurefile);

my ($input, $output);
my $pid = open2($output, $input, $command, @args) 
    or die "Failed with error $!\n";

my $thread = async {
    print join(",", qw(seqid start end strand read feature name)),"\n";
    for(;;) {
        my $line = <$output>; # should block here and wait for output?
        last if !defined $line; # end of stream reached?
        print STDERR "Got line $line\n";
        # Do some format conversion...
        chomp $line;
        my @cols = split /\t/, $line;
        # print a modified line to the tool
        print join(",",@cols[0,1,2,5,3,8]),"\n";
    }
    close($output)
};

{
    open (my $infh, '<', $infile) or die "Can't open $infile\n";
    while (<$infh>){
        # format conversion for ouput
        chomp;
        my @cols = split /\t/;
        print $input join ("\t", @cols[0,2,3,1,5,4]),"\n";
    }
    close ($input);
}

$thread->join();
waitpid ($pid, 0);

但是,脚本仍然以同样的方式卡住,我也被卡住了。我也无法弄清楚如何在这个例子中使用IPC::run

我做错了什么?我误解了线程吗?


编辑:花更多时间调试脚本(以及来自amon的帮助),我发现我能够从$output检索行。但是,脚本永远不会完成,并且在收到所有输出后似乎挂起。我认为现在这是我唯一的问题。

1 个答案:

答案 0 :(得分:1)

这更像是一个长篇评论。

我在精简版中尝试了您的代码。我删除了转换代码,使用Unix yes命令作为无限数据源并将输出打印到/dev/null,因为我们目前对输出不感兴趣,但是在程序工作中。作为overlapFeatures的替代品,我使用cat来传递未更改的数据。

use strict; use warnings; use IPC::Open2; use threads;

my $command = "cat";
my @args = ();

my ($input, $output);
my $pid = open2($output, $input, $command, @args) 
  or die "Failed with error $!\n";

my $thread = async {
  print $_ while defined($_ = <$output>);
  close($output)
};

{
  my $c=0;
  open (my $infh, "-|", "yes") or die;
  open my $null, ">/dev/null" or die;
  while (<$infh>){
    $c++;
    print $null $_;
    if ($c >= 1_000_000) {
      print "\n==another million==\n\n";
      $c=0
    }
  }
  close ($input);
}

$thread->join();
waitpid ($pid, 0);

一百万行(字面意思),我打印一条状态消息,断言IO仍在工作。

结果

使用Perl 12.4在Ubuntu Linux上测试,给定的脚本完美无瑕。因此可以合理地假设问题不在于IPC代码,而是在数据格式转换,包装程序或数据质量(yes输出字符串"1\n",每行产生许多小数据行(每组约2MB,每行2字节))

结论

可能是您正在运行其他配置。如果您正在运行* nix,请断言我使用的脚本也适用于您。如果没有,请明确说明此配置并尝试运行等效脚本。

也可以将你的包装器分成两个脚本,至少用于测试,所以你可以运行类似

的东西
$ convert-to | overlapFeatures | convert-from

这会将所有IPC委托给shell,并断言转换正在运行,并且架构是可实现的。

其他不可思议的想法集思广益:

(1)close操作何时执行?可能是因为一些奇怪的原因,循环的一端过早退出? print STDERR "Closing down xx\n"之前的close可能很有趣。 (2)open2async是否成功生成了他们的进程/线程并且返回控制流?偏执我会在他们之后加上另一个print STDERR ...... (3)您是否从脚本中获取任何数据,或者在一段时间后数据流变干了?

修改

在所有书写结束都是EOF d之前,管道不会产生close。因此,所有线程都应关闭它们未使用的任何内容:

my $thread = async {
  close $input;
  print $_ while defined($_ = <$output>);
  close($output)
};

{
  close $output;
  my $c=0;
  open (my $infh, "-|", "yes") or die;
  open my $null, ">/dev/null" or die;
  while (<$infh>){
    $c++;
    print $null $_;
    ...