1

我编写了一个 Perl 脚本,使用WWW::Mechanize它从文本文件中读取 URL 并一一连接到它们。在每个操作中,它都会解析网页的内容,寻找一些特定的关键字,如果找到,则会将其写入输出文件。

为了加快这个过程,我使用Parallel::ForkManagerMAX_CHILDRENset to 3。虽然我观察到速度有所提高,但问题是,一段时间后脚本崩溃了。Perl.exe进程被杀死并且它不显示任何特定的错误消息。

我已经多次运行该脚本以查看它是否总是在同一点失败,但是失败点似乎是间歇性的。

请注意,我已经处理了任何内存泄漏,WWW::Mechanize如下HTML::TreeBuilder::XPath所示:

  1. 对于WWW::Mechanize,我设置stack_depth(0)它不缓存访问页面的历史记录。
  2. HTML::TreeBuilder::XPath,完成后我删除根节点。这种方法帮助我解决了另一个类似脚本中的内存泄漏问题,该脚本不使用fork.

这是脚本的结构,我这里只提到了相关部分,如果需要更多细节来解决问题,请告诉我:

#! /usr/bin/perl

use HTML::TreeBuilder::XPath;
use WWW::Mechanize;
use warnings;
use diagnostics;
use constant MAX_CHILDREN => 3;

open(INPUT,"<",$input) || die("Couldn't read from the file, $input with error: $!\n");
open(OUTPUT, ">>", $output) || die("Couldn't open the file, $output with error: $!\n");

$pm = Parallel::ForkManager->new(MAX_CHILDREN);

$mech=WWW::Mechanize->new();
$mech->stack_depth(0);

while(<INPUT>)
{
chomp $_;
$url=$_;

$pm->start() and next;

$mech->get($url);

if($mech->success)
{
    $tree=HTML::TreeBuilder::XPath->new();
    $tree->parse($mech->content);

    # do some processing here on the content and print the results to OUTPUT file

    # once done then delete the root node

    $tree->delete();
}

$pm->finish();

print "Child Processing finished\n"; # it never reaches this point!

}

$pm->wait_all_children; 

我想知道,为什么这个 Perl 脚本在一段时间后总是失败?为了便于理解,我在 fork manager 的完成方法之后添加了一个 print 语句,但是它没有打印出来。我也使用wait_all_children了方法,因为根据 CPAN 上模块的文档,它将等待父进程的所有子进程的处理结束。

我不明白为什么wait_all_children方法放在循环之外whilefor如文档中所观察到的那样),因为所有处理都发生在循环内。

谢谢。

4

2 回答 2

2

至于为什么这段代码是用一个带有startandfinish调用的主作业循环编写的,然后是一个wait_all_children外部循环。它是这样工作的:

  1. <INPUT>父进程在每个循环开始时获取下一个作业。
  2. 父进程运行start,这导致子进程分叉。此时,您有 2 个进程,每个进程都在完全相同的时间点运行完全相同的代码。3a。父进程点击它or next并跳回顶部以读取下一个<INPUT>并重新开始该进程。3b。子进程不会命中or next并继续运行您提供的代码,直到它命中子进程finish退出的位置。
  3. 同时,父进程正忙于遍历循环并每次创建一个子进程。在分叉 3 个孩子(或任何你设置的限制)之后,它会阻塞,直到其中一个孩子退出。此时,它会立即产生一个新的孩子(每次都会为每个孩子执行步骤 3b)。
  4. 当父级的作业用完时,它会跳出 while 循环(它本身从未运行过任何东西),然后等待所有剩余的子级退出。

正如您所看到的,finish调用 after 循环中的任何代码都不会在父级(因为它在循环内之后不做任何事情or next)或子级(因为它们在 退出finish)中运行。

我从来没有使用过Parallel::ForkManager,但是run_on_finished如果你想在最后放置一个 print 语句,你可以在最后放置一个钩子来运行一些代码。

不过,为了找到问题所在,我建议将所有代码包装在一个或使用Try::Tinystart之间,然后找出错误,看看是否有异常发生在其中。不过,我希望孩子死后会出现这种情况,所以我不确定这是否会有所帮助。finishevalwarnSTDERR

不过,值得一试。这是我在代码中的建议,仅显示我将从中捕获异常的部分:

# At the top add
use Try::Tiny;

# Later in your main loop

$pm->start() and next;

try {

    $mech->get($url);

    if($mech->success)
    {
        $tree=HTML::TreeBuilder::XPath->new();
        $tree->parse($mech->content);

        # do some processing here on the content and print the results to OUTPUT file

        # once done then delete the root node

        $tree->delete();
    }
}

catch {
    warn "Bad Stuff: ", $_;
};

$pm->finish();

这可能有助于向您展示出了什么问题。

如果它没有帮助,您可以尝试移动该try块以包含更多程序(就像该行之后的几乎所有程序use Try::Tiny),看看这是否说明了任何事情。

于 2012-08-04T02:56:08.947 回答
0

$pm->wait_all_children;函数调用等待“ALL”子进程结束并放置阻塞锁。我不确定你$mechif()语句中做了什么样的错误处理,但你可能想重新访问它。

于 2012-08-04T03:32:54.970 回答