我正在使用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);
}
答案 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)
循环,然后在循环体内出列。重要的是:
eval
了get
。如果请求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方法中做的最后一件事。
关于分叉,你是否声称它在分叉时永远不会失败,分叉过程是否表示非零'退出状态'失败。您还确定在使用线程时不会超载网站,代理,网络等。