为啥 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

使用 Parallel::ForkManager 并行执行命令

在 Perl 中,如何验证 Parallel::ForkManager 的每个子项是不是都完成了它的工作?