为啥 Parallel::ForkManager 创建子进程很好,但不能并行处理它们
Posted
技术标签:
【中文标题】为啥 Parallel::ForkManager 创建子进程很好,但不能并行处理它们【英文标题】:Why is Parallel::ForkManager creating sub-processes fine, but not processing them in parallel为什么 Parallel::ForkManager 创建子进程很好,但不能并行处理它们 【发布时间】:2014-07-08 17:50:38 【问题描述】:在处理一些遗留代码时,我是否被以下问题难住了。
我们有一个程序可以自动将配置推送到多个路由器。执行此操作的 Perl 程序使用 Parallel::ForkManager
库来完成这些推送的并行化。
在程序的早期发生以下情况......
$p = new Parallel::ForkManager($multi);
创建分叉的主循环如下...
foreach my $node (@files)
# only if we're running in parallel
if (ref($p))
$p->start() and next;
# ship the file & path off to the main sub that does
# all the heavy lifting
do_push($node);
# only if we're running in parallel
if (ref($p))
$p->finish();
# only if we're running in parallel
if (ref($p))
$p->wait_all_children();
do_push($node)
子程序太长,这里就不详述了,简而言之,它为节点建立一个日志文件,然后通过 ssh 连接到路由器并推送配置。然后它退出并完成记录。
从表面上看,一切似乎都在工作,进程分叉,结果符合预期。
不过……
虽然创建了单独的进程,但路由器的连接和配置的推送不会并行发生。通过netstat -an
很容易验证 ssh 连接仅按顺序发生,而不是并行发生。只有在一个连接关闭后,下一个路由器才会连接。简而言之,我一次只能获得一个 ssh 连接。
有谁知道为什么会这样,或者从哪里着手解决这个问题?
[编辑]
基于cmets,这里总结一下子程序中发生的事情。出于空间和安全原因,我无法将其全部放在这里。
子程序的开始如下...
sub do_push
my($data) = @_;
my($path,$router) = @$data;
$|++; # hopefully speed things up
($DEBUG or ($v > 2)) && print STDERR "PROCESSING: $router ($path)\n";
接下来的过程是……
打开一个日志文件,写入它。 实例化连接到路由器的模块。 连接到路由器。 推送命令,验证命令,如果成功则提交。 一直记录到日志文件中。子程序结束如下...
# finished with this router...
# append our archive file
my $ts = tv_interval($t0, [gettimeofday()]);
(!$DEBUG && $archive) && print S "ROUTER: $router:$ts:$disposition\n";
($DEBUG or ($v > 2)) && print STDERR "ROUTER COMPLETE: $router -> $ts sec\n";
# unlock and close our files
(($DEBUG > 1) or ($v > 2)) && print STDERR "UNLOCK AND CLOSE: $archfile\n";
(($DEBUG > 1) or ($v > 2)) && print STDERR "UNLOCK AND CLOSE: $summary\n";
(!$DEBUG && $archive) && flock(F, LOCK_UN);
(!$DEBUG && $archive) && flock(S, LOCK_UN);
(!$DEBUG && $archive) && close(F);
(!$DEBUG && $archive) && close(S);
return;
如果在调试模式下运行子例程中的第一个调试语句:"PROCESSING: $router ($path)\n"
仅在前一个路由器完全完成子例程后打印。肯定有有多个进程分叉,我已经验证了。只是“某事”阻止进程运行,直到前一个进程完成。我正在努力寻找那个“东西”是什么。
感谢任何帮助。
【问题讨论】:
你是说程序只有一个子进程被创建一次(如使用ps
所见)?如果是这样,$multi
必须是 1
。如果不是,则与 P::FM 无关。
@ikegami 不,它正在生成多个分叉进程,而不仅仅是一个。
那么 P::FM 工作正常。如果您的代码中的其他地方存在问题,我们需要了解更多信息。
@ikegami 不幸的是,通过子例程调用的是 1000 行代码,以及多个模块、文件访问、解密库、通过 ssh 的 telnet 进程等。我需要一些时间尝试提炼正在发生的事情,主要是我正在寻找一个起点。我理解您的担忧。
要找出阻塞的原因,只需在 sub 中添加跟踪语句即可。 (在日志消息中包含$$
或记录到名称包含它的文件,以便您知道哪个进程正在记录什么。)
【参考方案1】:
[已解决]
我讨厌回答自己的问题,但我确实找到了阻止的原因...
原来问题是文件访问问题。在do_push()
循环中,程序正在记录到两个文件。一个文件是记录文件本身的结果,这个文件是子进程独有的,没问题。但是,该程序也正在写入“摘要”文件。所有子进程都在记录这个文件。
第一个线程会打开文件,记录一些内容,连接到路由器并推送命令。完成后,它将关闭文件。当然,其他线程会阻塞,等待访问“摘要”文件。
对于小型路由器推送,这一切都发生得如此之快,以至于这一切似乎都是并行发生的。只有当我们不得不向每个路由器推出 1000 条线路时,才发现这不是并行进行的。
【讨论】:
以上是关于为啥 Parallel::ForkManager 创建子进程很好,但不能并行处理它们的主要内容,如果未能解决你的问题,请参考以下文章
Parallel::ForkManager - 在父子之间共享哈希的更好方法是啥? [复制]
如何在完成时使用 Parallel::ForkManager 重新启动子进程
Perl Parallel::ForkManager ,fork 条件改变需要很长时间
如何在 perl 中安装 parallel-forkmanager