【问题标题】:perl threads exiting abnormallyperl 线程异常退出
【发布时间】:2012-11-18 08:51:34
【问题描述】:

我正在使用 perl 的 threads 模块和我正在开发的简单爬虫,因此我可以并行下载页面。有时,我会收到如下错误消息:

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);
}

【问题讨论】:

  • 您是否确保为每个线程获得所有内容的新实例?审核代码以确保在每个线程中没有共享任何内容,每个线程都需要初始化自己的 perl 对象,应该传入很少的内容(如仅 URL),并且不应访问共享的全局变量。我怀疑这个问题只是设计错误。
  • @DarrylMiles,我在上面发布了一个导致错误的示例脚本。我很确定此示例脚本中没有共享任何内容,但我仍然收到错误消息。
  • 好的脚本至少我们可以看到你在做什么。你只有4个项目放在$workq中,但是你启动了10个线程,每个线程可以处理多个项目。第 4 个线程不太可能看到任何工作要做。目标站点是否由您拥有/管理?你怎么知道他们没有连接洪水控制?如果它们是连续的,则原始错误中的线程编号表示超过 10。也许将当前线程开始/停止和工作的总数添加到子程序中,还在所有输出中发出线程-> tid()。你也许你更好地看到问题。

标签: multithreading perl exit


【解决方案1】:

我玩了一下你的资源并想出了这个:

#!/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 表示线程终止:一旦false 值出列,循环条件无论如何都是假的并且线程终止。如果你想传递一个特殊的字符串来终止信号,你应该循环使用while (1),然后在循环体中出列。

重要:

  • 为了消除那些讨厌的错误,我eval'd get。如果die的请求,我的帖子不会跟风,而是保持冷静继续进行。

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

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

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

【讨论】:

  • 太棒了!完美运行。非常感谢:)
【解决方案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)

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

【讨论】:

    【解决方案3】:

    perl 确实有一个机制来中止和执行 fatal()。但我认为你的情况并非如此。

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

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

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

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

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

    【讨论】:

    • hmmm...但是,如果我线性下载 url 或通过分叉进行下载,则不会给出此错误消息。没有办法阻止线程终止?
    • 是的,当分叉进程时不会不规则地失败。而且我不太可能超载网站的代理/网络/等,因为我的测试数据中的每个 url 都在不同的网站上。不,分叉并不表示非零退出状态的失败。它正常终止。
    • 如果您确定,可能是任何东西,程序错误、设计错误、perl 错误。您需要深入了解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-09
    • 1970-01-01
    • 2013-04-11
    • 2011-05-04
    • 2017-10-01
    • 1970-01-01
    相关资源
    最近更新 更多