Perl多线程 - 工作线程停止工作

时间:2016-08-08 15:27:52

标签: multithreading perl

我有以下perl代码,它对两个外部生物信息学程序进行异步调用。首先,它运行一个blastJob,然后它从中获取结果并运行一个exonerateJob。我已经调整了previous question中关于将代码移动到多线程方法的代码。

问题令人沮丧,因为它只在运行几个小时后才会发生。我将让程序在一夜之间运行,并在早上发现exonerateJobs不再运行,但新的blastJobs仍在启动。没有报告错误或任何事情。另一个信息是,我已经回去测试输入查询,其中日志显示exonerateJobs停止工作。如果我通过它运行少量查询,程序就完成了,即使它们是先前似乎导致问题的查询。由于我不太熟悉多线程的规则,我想知道我的方法是否存在问题,或者它是否可能是被调用的外部程序的问题。这是代码:

#Asynchronous calls to blast and exonerate
{
   my $blast_request_queue = Thread::Queue->new();
   my $exonerate_request_queue = Thread::Queue->new();

   my @blast_threads;
   for (1..NUM_BLAST_WORKERS) {
      push @blast_threads, async {
         while (my $q = $blast_request_queue->dequeue()) {
            my @results = blastJob($q, $blastopts_ref);
            foreach (@results) {
               my @args = ($q, $_);
               $exonerate_request_queue->enqueue(\@args);
            }
         }

         $exonerate_request_queue->end(); # I've tried with and without this line, the result seems to be the same
      };
   }

   my @exonerate_threads;
   for (1..NUM_EXONERATE_WORKERS) {
      push @exonerate_threads, async {
         while (my $args_ref = $exonerate_request_queue->dequeue()) {
            my ($queryFile, $targetName) = @$args_ref; #De-reference args
            my $regex = qr/\Q$targetName\E/;
            #Check for target file
            my ($file_match) = grep { $_ =~ $regex } keys %targets;
            if ($file_match) {
                my $targetFile = $options{'t'} . $file_match;
                my $result = exonerateJob($queryFile, $targetFile, $exonopts_ref);
                #Print result to file after job is finished
                my ($Qname, $Qpath, $Qsuffix) = fileparse($queryFile);
                my $outFN = $Qname . ".exonerate_out";
                open(OUTFH, ">>$outFN") or print STDERR "Can't open $outFN: $!";
                print OUTFH $result;
            } else {
                print STDERR "Target file not found: $targetName. Can't run exonerate";
            }
         }
      };
   }

    foreach (@queries) {
        #Concatenate query path with name
        my $queryFile = $options{'q'} . $_;
        $blast_request_queue->enqueue($queryFile);
    }
    #my $queryFile = $options{'q'} . $queries[3];
    #$blast_request_queue->enqueue($queryFile);

   $blast_request_queue->end();    
   $_->join() for @blast_threads;
   $exonerate_request_queue->end();
   $_->join() for @exonerate_threads;
}

#I'm using IPC::Run to launch the programs.
#There is some error handling which I believe should catch any probs
sub blastJob {
    my ($query, $blastopts_ref) = @_;
    #De-reference blast options
    my @blastCmd = @$blastopts_ref;
    my ($blastOut, $err); #for blast output
    #Add query information after first blast option
    splice(@blastCmd, 1, 0, ("-query", $query));
    my ($Qname, $Qpath, $Qsuffix) = fileparse($query);
    print "Running $blastCmd[0]: query $Qname...\n";
    run \@blastCmd, \undef, \$blastOut, \$err;
    if ($err) {
        print "Error in BLAST query $Qname. $err\n";
    }
    my @results = split("\n", $blastOut);
    return uniq(@results);
}

sub exonerateJob {
    my ($query, $target, $exonopts_ref) = @_;
    #De-reference exonerate options
    my @exonCmd = @$exonopts_ref;
    my ($exonOut, $err); #for exonerate output
    #Add program, query, and target information to exonerate options
    unshift (@exonCmd, ("exonerate", "-q", $query, "-t", $target));
    my ($Qname, $Qpath, $Qsuffix) = fileparse($query);
    my ($Tname, $Tpath, $Tsuffix) = fileparse($target);
    eval {
        print "Running exonerate: query $Qname, target $Tname...\n";
        run \@exonCmd, \undef, \$exonOut, \$err, timeout(240);
        if ($err) {
            print STDERR "Error in exonerate query $Qname, target $Tname. $err\n";
        }
    };
    if ($@ =~ /timeout/) {
        print STDERR "Error: timeout in exonerate query $Qname, target $Tname\n";
    }

    return $exonOut;
}

2 个答案:

答案 0 :(得分:1)

如果没有您的来源信息,我无法对其进行测试 - 但我的钱会在您第一次上映时:

$exonerate_request_queue->end();

在那个异步块中。

因为我觉得那里很有可能,所以尽快关闭

$blast_request_queue->end(); 

然后线程将很快退出,关闭'输出'队列,这样做 - 意味着你丢失任何待处理的东西,因为队列已关闭。

答案 1 :(得分:1)

当您只是在工作人员代码周围添加die时,您已经遇到了很多麻烦以避免eval BLOCK

更改

my $result = job1($job);
$job2_request_queue->enqueue($result);

my $result = eval { job1($job) };
if ($@) {
   warn("Job failed: $@");
} else {
   $job2_request_queue->enqueue($result);
}

这更可靠。例如,run会抛出一个会杀死你孩子的异常。

另外,正如Sobrique所提到的,不应该添加$exonerate_request_queue->end();的最顶层实例。这可以防止将更多工作添加到队列中(并且一旦执行了当前队列中的所有工作,就会通知无效工作人员退出)。这应该只在每个爆破工人退出后才能完成,但是这一添加导致一旦第一个爆破工人退出就会完成。