Perl 与 Parallel::ForkManager 和 WWW::Mechanize 一起崩溃

Posted

技术标签:

【中文标题】Perl 与 Parallel::ForkManager 和 WWW::Mechanize 一起崩溃【英文标题】:Perl crashing with Parallel::ForkManager and WWW::Mechanize 【发布时间】:2012-08-04 02:13:00 【问题描述】:

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

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

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

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

    对于WWW::Mechanize,我设置了stack_depth(0),这样它就不会缓存访问页面的历史记录。 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 循环之外(正如文档中所观察到的那样),因为所有处理都在循环内进行。

谢谢。

【问题讨论】:

【参考方案1】:

至于为什么这段代码是用带有startfinish 调用的主作业循环编写的,然后在循环外加上wait_all_children。它的工作原理是这样的:

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

如您所见,调用finish 之后循环中的任何代码都不会在父级(因为在循环内or next 之后它不执行任何操作)或子级(因为它们退出finish)。

我从未使用过Parallel::ForkManager,但如果您想在末尾添加一个打印语句,您可以在末尾添加一个run_on_finished 挂钩来运行一些代码。

不过,要找到问题所在,我建议将 startfinish 之间的所有代码包装在 eval 中,或者使用 Try::Tiny 和 warn 排除错误以查看是否发生异常在那里打破它。不过,我希望孩子死后这些事情会出现在STDERR,所以我不确定这是否会有所帮助。

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

# 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 行之后的几乎所有程序),看看这是否说明了什么。

【讨论】:

您好,感谢您的帮助。我尝试放置 try 和 catch 块以查看错误消息的更多详细信息,但它不起作用。我什至尝试在其中包含更多代码,但这会使脚本更早失败,而不会提供任何错误消息详细信息。 try 块只是试图揭示问题。如果无法看到 # do some processing here 正在做什么,很难提供更多帮助。这里的分叉使一些调试变得乏味。您可以添加一堆警告语句,例如warn "[$$] After mech get\n"warn "[$$] After tree delete\n",以了解它的失败之处。 $$ 将允许您按子进程 ID 对警告日志条目进行分组。 可以私信给你脚本吗?【参考方案2】:

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

【讨论】:

我认为在任何时候都只会有一个父进程,并且最多会产生 3 个子进程。当任何一个子进程完成工作时,就会产生一个新的子进程。我尝试过使用和不使用 wait_all_children 方法。对于 $mech 的错误处理,我在 eval 中放置了 $mech->get($url) ,然后检查 $@ 是否为空。如果不是,则显示错误消息并继续。

以上是关于Perl 与 Parallel::ForkManager 和 WWW::Mechanize 一起崩溃的主要内容,如果未能解决你的问题,请参考以下文章

实用技能GET!Perl 语言模块的安装与使用

将 Perl 与 xm_perl 模块一起使用时出错

perl的更新与模块的安装

安装脚本的 shell 脚本与 perl - perl 有多普遍?

Perl零零八速成系列---字符串操作与排序

Perl 7正在路上,6呢?