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::Mechanize
和html::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
方法位于 while
或 for
循环之外(正如文档中所观察到的那样),因为所有处理都在循环内进行。
谢谢。
【问题讨论】:
【参考方案1】:至于为什么这段代码是用带有start
和finish
调用的主作业循环编写的,然后在循环外加上wait_all_children
。它的工作原理是这样的:
-
父进程在每个循环开始时从
<INPUT>
获取下一个作业。
父进程运行start
,导致子进程fork。此时,您有 2 个进程,每个进程都在完全相同的时间点运行完全相同的代码。
3a。父进程点击or next
并跳回顶部以读取下一个<INPUT>
并重新开始该进程。
3b。子进程没有命中or next
,并继续运行您提供的代码,直到它命中finish
,子进程退出。
与此同时,父进程正忙于遍历循环并每次都创建一个子进程。在分叉 3 个孩子(或任何你设置的限制)之后,它会阻塞,直到其中一个孩子退出。此时,它会立即生成一个新子代(每次都会为每个子代执行第 3b 步)。
当父级的作业用完时,它会跳出 while 循环(它本身从未运行过任何东西),然后等待所有剩余的子级退出。
如您所见,调用finish
之后循环中的任何代码都不会在父级(因为在循环内or next
之后它不执行任何操作)或子级(因为它们退出finish
)。
我从未使用过Parallel::ForkManager,但如果您想在末尾添加一个打印语句,您可以在末尾添加一个run_on_finished
挂钩来运行一些代码。
不过,要找到问题所在,我建议将 start
和 finish
之间的所有代码包装在 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->wait_all_children;
函数调用等待“ALL”子进程结束并放置阻塞锁。我不确定您在 if()
语句中对 $mech
做了什么样的错误处理,但您可能想重新访问。
【讨论】:
我认为在任何时候都只会有一个父进程,并且最多会产生 3 个子进程。当任何一个子进程完成工作时,就会产生一个新的子进程。我尝试过使用和不使用 wait_all_children 方法。对于 $mech 的错误处理,我在 eval 中放置了 $mech->get($url) ,然后检查 $@ 是否为空。如果不是,则显示错误消息并继续。以上是关于Perl 与 Parallel::ForkManager 和 WWW::Mechanize 一起崩溃的主要内容,如果未能解决你的问题,请参考以下文章