3

我正在使用 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 的线程时,我必须采取一些额外的预防措施吗?谢谢!

更新:

我已经找到了这些异常终止的来源,而且似乎每当我使用LWP::UserAgent. 如果我删除下载网页的方法调用,那么错误就会停止。

示例脚本

下面的脚本会导致我所说的一个错误。最后一个 URL 将超时,导致应该只是 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);
}
4

3 回答 3

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,我的线程不会效仿,而是保持冷静并继续进行。

因为getting URL 实际上可能会死。如果我们查看LWP::Protocol::http 源代码的第 51 行,我们会看到如果无法为连接创建套接字,则会引发致命错误。当无法解析主机名时,可能会发生这种情况。

在我的代码中,我决定忽略错误(因为我已经打印了状态行)。根据问题,您可能希望再次重试该 URL,或提供更多信息警告。有关错误处理的一个很好的示例,请参阅链接源。

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

于 2012-11-18T13:03:59.667 回答
2

看起来该get方法正在设置$@,即使它没有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)

然后线程退出似乎$@被设置为异常。如果您$@在退出线程之前(或local $@Handle_Work中或eval周围get)重置,则线程干净地退出。

于 2012-11-18T13:05:00.163 回答
0

好吧 perl 确实有一个机制来中止和执行 fatal()。但我不认为这是你的情况。

如果您查看threads.pl 第101 行,这可能是线程退出方法,并且使用非零退出状态可能被视为异常情况。

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

对您而言,措辞的选择令人担忧并引起关注,但如果您将消息更改为:“线程 123 未完成,表明成功”,它可能看起来不那么令人担忧,并且更符合正在发生的情况。

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

关于分叉,您是否声称它在分叉时永远不会失败,并且分叉进程是否指示非零“退出状态”的失败。另外,您确定在使用线程时没有使网站、代理、网络等超载。

于 2012-11-18T08:59:08.077 回答