perl之如何多线程查看 Perl 中是否存在网页

mayingbao 阅读:43 2025-06-02 22:19:02 评论:0

我正在编写一个 Perl 脚本,它接收一个 URL 列表并检查它们是否存在。 (请注意,我只关心它们是否存在;我不关心它们的内容是什么。这是程序的重要部分。

use LWP::Simple qw($ua head); 
 
if (head($url)) 
{ 
    $numberAlive ++; 
} 
else 
{ 
    $numberDead ++; 
} 

现在程序运行良好;但是,我希望它运行得更快。因此,我正在考虑将其设为多线程。我假设我的程序的缓慢部分是为每个 URL 联系服务器;因此,我正在寻找一种在等待第一个响应时可以向列表中其他网页的 URL 发送请求的方法。我怎样才能做到这一点?据我所知, head例程没有可以在服务器响应后调用的回调。

请您参考如下方法:

从熟悉的前题开始。

#! /usr/bin/env perl 
 
use strict; 
use warnings; 
 
use 5.10.0;  # for // (defined-or) 
 
use IO::Handle; 
use IO::Select; 
use LWP::Simple; 
use POSIX qw/ :sys_wait_h /; 
use Socket; 

全局常量控制程序执行。
my $DEBUG = 0; 
my $EXIT_COMMAND = "<EXIT>"; 
my $NJOBS = 10; 

要检查的 URL 每行到达一个套接字的 worker 端。对于每个 URL,worker 调用 LWP::Simple::head确定资源是否可获取。然后,worker 将格式为 url 的行写回套接字。 : *status* 其中 *status* 是 "YES""NO" 表示空格字符。

如果 URL 是 $EXIT_COMMAND ,然后 worker 立即退出。
sub check_sites { 
  my($s) = @_; 
 
  warn "$0: [$$]: waiting for URL" if $DEBUG; 
 
  while (<$s>) { 
    chomp; 
    warn "$0: [$$]: got '$_'" if $DEBUG; 
    exit 0 if $_ eq $EXIT_COMMAND; 
    print $s "$_: ", (head($_) ? "YES" : "NO"), "\n"; 
  } 
 
  die "NOTREACHED"; 
} 

要创建一个worker,我们首先创建一个 socketpair .父进程将使用一端,每个 worker (子)将使用另一端。我们禁用两端的缓冲并将父端添加到我们的 IO::Select 实例。我们还记录了每个 child 的进程 ID,以便我们可以等待所有工作人员完成。
sub create_worker { 
  my($sel,$kidpid) = @_; 
 
  socketpair my $parent, my $kid, AF_UNIX, SOCK_STREAM, PF_UNSPEC 
    or die "$0: socketpair: $!"; 
  $_->autoflush(1) for $parent, $kid; 
 
  my $pid = fork // die "$0: fork: $!"; 
  if ($pid) { 
    ++$kidpid->{$pid}; 
    close $kid or die "$0: close: $!"; 
    $sel->add($parent); 
  } 
  else { 
    close $parent or die "$0: close: $!"; 
    check_sites $kid; 
    die "NOTREACHED"; 
  } 
} 

为了分派(dispatch) URL,父级获取尽可能多的可用阅读器,并从作业队列中分发相同数量的 URL。在作业队列为空后剩余的任何工作人员都会收到退出命令。

请注意 print如果底层工作人员已经退出,则将失败。父级必须忽略 SIGPIPE以防止立即终止。
sub dispatch_jobs { 
  my($sel,$jobs) = @_; 
 
  foreach my $s ($sel->can_write) { 
    my $url = @$jobs ? shift @$jobs : $EXIT_COMMAND; 
    warn "$0 [$$]: sending '$url' to fd ", fileno $s if $DEBUG; 
    print $s $url, "\n" or $sel->remove($s); 
  } 
} 

由时间控制达到 read_results , worker 已经创建并接受了工作。现在父级使用 can_read 等待一个或多个 worker 的结果。定义的结果是当前工作人员的回答,未定义的结果意味着 child 已经退出并关闭了套接字的另一端。
sub read_results { 
  my($sel,$results) = @_; 
 
  warn "$0 [$$]: waiting for readers" if $DEBUG; 
  foreach my $s ($sel->can_read) { 
    warn "$0: [$$]: reading from fd ", fileno $s if $DEBUG; 
    if (defined(my $result = <$s>)) { 
      chomp $result; 
      push @$results, $result; 
      warn "$0 [$$]: got '$result' from fd ", fileno $s if $DEBUG; 
    } 
    else { 
      warn "$0 [$$]: eof from fd ", fileno $s if $DEBUG; 
      $sel->remove($s); 
    } 
  } 
} 

parent 必须跟踪现场工作人员以收集所有结果。
sub reap_workers { 
  my($kidpid) = @_; 
 
  while ((my $pid = waitpid -1, WNOHANG) > 0) { 
    warn "$0: [$$]: reaped $pid" if $DEBUG; 
    delete $kidpid->{$pid}; 
  } 
} 

运行池执行上面的 subs 以调度所有 URL 并返回所有结果。
sub run_pool { 
  my($n,@jobs) = @_; 
 
  my $sel = IO::Select->new; 
  my %kidpid; 
  my @results; 
 
  create_worker $sel, \%kidpid for 1 .. $n; 
 
  local $SIG{PIPE} = "IGNORE";  # writes to dead workers will fail 
 
  while (@jobs || keys %kidpid || $sel->handles) { 
    dispatch_jobs $sel, \@jobs; 
 
    read_results $sel, \@results; 
 
    reap_workers \%kidpid; 
  } 
 
  warn "$0 [$$]: returning @results" if $DEBUG; 
  @results; 
} 

使用示例主程序
my @jobs = qw( 
  bogus 
  http://stackoverflow.com/ 
  http://www.google.com/ 
  http://www.yahoo.com/ 
); 
 
my @results = run_pool $NJOBS, @jobs; 
print $_, "\n" for @results; 

输出是

假的:没有
http://www.google.com/:是的
http://stackoverflow.com/:是的
http://www.yahoo.com/:是的


标签:多线程
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号