我在Perl中编写了一个使用多线程的程序。我正在使用这个程序来理解Perl中如何实现多线程。
首先简要概述程序打算做什么:它将从文本文件中一次读取一个URL列表。对于每个URL,它将调用子例程(将URL作为参数传递)并向其发送HTTP HEAD请求。收到HTTP响应标头后,它将从响应中打印服务器标头字段。
对于每个URL,它启动一个调用上述子例程的新线程。
问题:主要问题是程序有时间歇性地崩溃。它在其他时间正常运行。它似乎是不可靠的代码,我相信有一种方法可以让它可靠地工作。
代码:
#!/usr/bin/perl
use strict;
use warnings;
use threads;
use WWW::Mechanize;
no warnings 'uninitialized';
open(INPUT,'<','urls.txt') || die("Couldn't open the file in read mode\n");
print "Starting main program\n";
my @threads;
while(my $url = <INPUT>)
{
chomp $url;
my $t = threads->new(\&sub1, $url);
push(@threads,$t);
}
foreach (@threads) {
$_->join;
}
print "End of main program\n";
sub sub1 {
my $site = shift;
sleep 1;
my $mech = WWW::Mechanize->new();
$mech->agent_alias('Windows IE 6');
# trap any error which occurs while sending an HTTP HEAD request to the site
eval{$mech->head($site);};
if($@)
{
print "Error connecting to: ".$site."\n";
}
my $response = $mech->response();
print $site." => ".$response->header('Server'),"\n";
}
问题:
如何让这个程序可靠地运行?偶然崩溃的原因是什么?
调用线程对象的join方法的目的是什么?
根据以下链接的文档,它将等待线程执行完成。我是否正确调用了连接方法?
http://perldoc.perl.org/threads.html
如果我必须在上述代码中包含任何良好的编程习惯,请告知我们。
我是否需要在代码中专门调用sleep(),还是不需要?
在C中,我们在调用CreateThread()之后调用Sleep()来开始执行线程。
关于崩溃:当上面的Perl代码意外崩溃并偶尔崩溃时,我收到错误消息:“Perl命令行解释器已停止工作”
崩溃的详情:
Fault Module Name: ntdll.dll
Exception Code: c0000008
上述异常代码对应于:STATUS_INVALID_HANDLE
也许这对应于线程的无效句柄。
我的Perl安装细节:
Summary of my perl5 (revision 5 version 14 subversion 2) configuration:
Platform:
osname=MSWin32, osvers=5.2, archname=MSWin32-x86-multi-thread
useithreads=define
操作系统的详细信息:Win 7 Ultimate,64位操作系统。
希望此信息足以找到问题的根本原因并更正代码。
答案 0 :(得分:4)
您的代码没有任何问题。可能你的期望有点太高了。
通过在同一操作系统进程中创建多个解释器实例来实现Perl的线程。这将每个线程中的Perl代码与所有其他代码隔离开来(它没有共享)。它没有(并且不能)做的是隔离不受perl控制的代码。也就是说,任何具有用C语言编写的组件的模块。例如,快速浏览WWW :: Mechanize表明它能够使用zlib进行压缩(如果已安装)。如果使用它,并且C代码没有足够的线程安全性,那么这可能是一个可能崩溃的问题。因此,如果您想确保您的Perl应用程序在线程下运行良好,您必须浏览它使用的所有模块(以及他们使用的所有模块)并检查它们是否没有非Perl部件或那些部件是线程安全的。对于大多数重要的程序来说,这是一项不合理的工作量(或者对可以使用的CPAN模块的不合理限制)。
这可能是Perl中线程没有被广泛使用的原因之一。
答案 1 :(得分:2)
我在perl中广泛使用多线程来构建大型系统。 您启动线程并等待它们完成的部分对我来说很好。
回答你的问题:
不需要睡眠。
你调用join的方式是正确的,它基本上会阻塞直到所有线程完成。
我会做以下事情:
尝试注释掉机械化代码。只是为了确保它不是造成这种情况的那个。可以在函数内部进行随机睡眠。看看你的脚本是否仍然崩溃。
尝试删除多线程并查看是否多次调用该函数(具有for循环或其他内容)会导致任何问题。
答案 2 :(得分:0)
跳出来的一个小“最佳实践”的东西是你使用三个参数打开(好)但是一个裸字文件句柄(嘘!)。我总是倾向于使用“和”和“或”而不是“&amp;&amp;”和“或”也是。它们是最低优先级的运算符,因此(对我来说,至少)最容易用来正确分割命令。我倾向于使用&amp;&amp;和||只在三元运算符内或等于右边,就像我的$ a = func()|| '默认';
所以我写的那条开放线:
open my $input, '<', 'urls.txt; or die "Couldn't open `urls.txt' for read: $!";
答案 3 :(得分:0)
我建议使用可重用的线程方法。请参阅此示例:Reusable threads demo
还要检查优秀的Thread :: Queue模块:
use threads;
use Thread::Queue;
my $q = Thread::Queue->new();
my $pq = Thread::Queue->new();
my $config = { number_of_threads => 10 };
my @threads = map { threads->create( \&worker, $q, $pq ) }
( 1 .. $config->{number_of_threads} );
push @threads, threads->create( \&controller, $q, $pq );
my @urls = read_urls($filename);
foreach my $url (@urls) {
process_url( $q, $url );
}
while ( my $pend = $q->pending() ) {
sleep 1;
}
$q->enqueue(undef) for @threads;
while ( my $pend = $pq->pending() ) {
sleep 1;
}
$pq->enqueue(undef);
foreach my $thr (@threads) {
$thr->join();
}
sub worker {
my ( $q, $pq ) = @_;
while ( my $url = $q->dequeue() ) {
my $result = check_url($url);
$pq->enqueue($result);
}
printf "Finishing tid(%s)\n", threads->tid;
return;
}
sub controller {
my ( $q, $pq ) = @_;
while ( my $result = $pq->dequeue() ) {
save_result($result);
}
printf "Finishing Controller tid(%s)\n", threads->tid;
return;
}
sub process_url {
my ( $q, $url ) = @_;
$q->enqueue($url);
return;
}