perl线程异常退出

时间:2012-11-18 08:51:34

标签: multithreading perl exit

我正在使用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);
}

3 个答案:

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

重要的是:

  • 为了解决那些讨厌的错误,我evalget。如果请求die,我的帖子不会效仿但保持冷静并继续。

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

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

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

答案 1 :(得分: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,则该主题会完全退出。

答案 2 :(得分:0)

好的perl确实有一种中止和致命的机制()。但我不认为你是这样的。

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

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

对你而言,选择单词是令人担忧的并引起关注,但是如果你将消息更改为:“线程123没有完成表示成功”它可能看起来不那么令人担忧,而且更符合正在发生的事情。

最好允许线程main方法返回(必要时在路上释放数据)。这不是使用threads :: exit,除非当然是在main方法中做的最后一件事。

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