我有以下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;
}
答案 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();
的最顶层实例。这可以防止将更多工作添加到队列中(并且一旦执行了当前队列中的所有工作,就会通知无效工作人员退出)。这应该只在每个爆破工人退出后才能完成,但是这一添加导致一旦第一个爆破工人退出就会完成。