2012-11-18 114 views
3

我正在使用perl的threads模块,它带有一个简单的搜寻器,我正在开发,因此我可以并行下载页面。 Ocasionally,我得到错误信息像这样的:perl线程异常退出

Thread 7 terminated abnormally: read timeout at /usr/lib64/perl5/threads.pm line 101. 
Thread 15 terminated abnormally: Can't connect to burgundywinecompany.com:80 (connect: timeout) at /usr/lib64/perl5/threads.pm line 101. 
Thread 19 terminated abnormally: write failed: Connection reset by peer at /usr/lib64/perl5/threads.pm line 101. 

当我线性运行该脚本无绪,我不会遇到这些错误。这些错误几乎看起来像是来自LWP::UserAgent模块,但他们似乎不应该导致线程异常退出。使用perl的线程时是否需要采取一些额外的预防措施?谢谢!

UPDATE:

我已经找到了这些异常终止的来源,它似乎是,每当我做使用LWP::UserAgent的请求。如果我删除方法调用来下​​载网页,则错误将停止。

示例脚本

下面的脚本会导致一个错误,我说的。最后浏览的网址就会超时,导致什么应该仅仅是HTTP :: Repsonse对象的一部分,而不是导致线程异常终止:

#!/usr/bin/perl 
use threads; 
use Thread::Queue; 
use LWP::UserAgent; 

my $THREADS=10; # Number of threads 
          #(if you care about them) 
my $workq = Thread::Queue->new(); # Work to do 

my @stufftodo = qw(http://www.collectorsarmoury.com/ http://burgundywinecompany.com/ http://beetreeminiatures.com/); 

$workq->enqueue(@stufftodo); # Queue up some work to do 
$workq->enqueue("EXIT") for(1..$THREADS); # And tell them when 

threads->create("Handle_Work") for(1..$THREADS); # Spawn our workers 

$_->join for threads->list; 

sub Handle_Work { 
    while(my $todo=$workq->dequeue()) { 
     last if $todo eq 'EXIT'; # All done 
     print "$todo\n"; 
     my $ua = LWP::UserAgent->new; 
     my $RESP = $ua->get($todo); 
    } 
    threads->exit(0); 
} 
+0

您是否确保为每个线程获取所有内容的新实例?审核代码以确保每个线程中都没有共享,每个线程都需要初始化自己的perl对象,很少应该传入(比如只有URL),并且不应该访问共享的全局变量。我怀疑这个问题只是设计错误。 –

+0

@DarrylMiles,我已经发布了上面的示例脚本,导致错误。我很确定在这个脚本文件中没有共享任何东西,但我仍然得到错误。 – srchulo

+0

好的脚本至少我们可以看到你在做什么。你只有4个项目放置在$ workq中,但是你启动了10个线程,每个线程可以在多个项目上工作。第四个线程不可能看到任何工作要做。目标网站是由您拥有/管理的吗?你怎么知道他们没有连接泛滥控制?原始错误中的线程编号指示超过10个(如果它们是连续的)。可以将子线程添加到当前线程启动开始/停止以及所有工作的总数中,也会在所有输出中发出threads-> tid()。也许你更好地看问题。 –

回答

3

我打了一下与你的源以及与此想出了:

#!/usr/bin/perl 

use 5.012; use warnings; 
use threads; use Thread::Queue; use LWP::UserAgent; 

use constant THREADS => 10; 

my $queue = Thread::Queue->new(); 
my @URLs = qw(http://www.collectorsarmoury.com/ 
       http://burgundywinecompany.com/ 
       http://beetreeminiatures.com/  ); 
my @threads; 

for (1..THREADS) { 
    push @threads, threads->create(sub { 
     my $ua = LWP::UserAgent->new; 
     $ua->timeout(5); # short timeout for easy testing. 
     while(my $task = $queue->dequeue) { 
      my $response = eval{ $ua->get($task)->status_line }; 
      say "$task --> $response"; 
     } 
    }); 
} 

$queue->enqueue(@URLs); 
$queue->enqueue(undef) for 1..THREADS; 
# ... here work is done 
$_->join foreach @threads; 

输出:

http://www.collectorsarmoury.com/ --> 200 OK 
http://burgundywinecompany.com/ --> 200 OK 
http://beetreeminiatures.com/ --> 500 Can't connect to beetreeminiatures.com:80 (timeout) 

输出而不eval

http://www.collectorsarmoury.com/ --> 200 OK 
http://burgundywinecompany.com/ --> 200 OK 
http://beetreeminiatures.com/ --> 500 Can't connect to beetreeminiatures.com:80 (timeout) 
Thread 2 terminated abnormally: Can't connect to beetreeminiatures.com:80 (timeout) 

LWP::Protocol::http::Socket: connect: timeout at /usr/share/perl5/LWP/Protocol/http.pm line 51. 

东西,但我不同的是:

不重要:

  • 我不exit我的线程;我只是在结尾(隐含return
  • 我为每个线程分配一个用户代理,而不是每个请求一个。

更好的风格:

  • 我用undef信号线程终止:一旦值出列,循环条件是无论如何假,线程终止。如果你想传递一个特殊的字符串来终止信号,你应该循环使用while (1),并在循环体内出队。

重要:

  • 要消除这些讨厌的错误,我eval倒是在get。如果请求die,我的线程不会跟风而是保持冷静并继续。

因为get可能导致死亡。如果我们查看source of LWP::Protocol::http的第51行,我们看到如果没有为连接创建套接字,将会引发致命错误。当主机名无法解析时,可能会发生这种情况。

在我的代码中,我决定忽略错误(因为我已经打印状态行)。根据问题,您可能需要重试该URL,或者提供更多信息的警告。查看链接的源代码以获取错误处理的一个很好的示例。

不幸的是,我无法重现您的确切错误(警告中给出的行指向threads->exit()类方法)。但在大多数情况下,使用eval应防止异常终止。

+0

太棒了!完美的作品。非常感谢 :) – srchulo

0

好Perl并有一个机制来中止并做致命的()。但我不认为这是你的情况。

如果你看一下threads.pl第101行,这可能是线程退出方法和使用非零退出状态可能被认为是一个异常情况。

我认为这些东西是无害的,'异常终止'的使用只是表明手术不是100%成功。这意味着您应该为那些操作未完成的线程规划和实施恢复方案。

对你来说,单词的选择是令人担忧和引起担忧的,但是如果你将消息改为:“线程123没有完成指示成功”,它可能看起来不那么令人震惊,更符合这种情况。

允许线程main方法返回(如果需要,在途中释放数据)也更好。这不是使用threads :: exit,除非当做main方法中的最后一件事情。

关于分叉,你是否声称它在分叉时永远不会失败,并且分叉的过程是否表示非零'退出状态'失败。当你使用线程时,你也确定你没有超载网站,代理,网络等等。

+0

嗯......但是如果我线性地下载URL或者如果我通过分叉来完成,这个错误信息是不会被给出的。没有办法阻止终止线程? – srchulo

+0

是的,分叉过程不会不规律地失败。而且我的网站的代理/网络/等也不太可能,因为我的测试数据中的每个网址都位于不同的网站。不,分岔并不表示失败时出现非零退出状态。它正常结束。 – srchulo

+0

如果您确定,可能是任何东西,程序错误,设计错误,perl错误。你需要深究。 –

2

它看起来像get方法是设置[email protected],即使它不die。你可以看到它是不会死的,把一些印刷品的get后:

my $RESP = $ua->get($todo); 
if($RESP->is_success) { 
    print "$todo success\n"; 
} else { 
    print "$todo failed: ".$RESP->status_line."\n"; 
} 

你可以看到打印失败后请求的线程退出之前仍然发生:

http://www.collectorsarmoury.com/ success 
http://burgundywinecompany.com/ success 
http://beetreeminiatures.com/ failed: 500 Can't connect to beetreeminiatures.com:80 (Connection timed out) 
Thread 3 terminated abnormally: Can't connect to beetreeminiatures.com:80 (Connection timed out) 

线程退出然后在[email protected]被设置为异常时出现拾取。如果您在退出线程之前重置[email protected](或local [email protected]Handle_Workeval围绕get),则线程干净地退出。

相关问题