如何从子进程传递变量(由 Parallel::ForkManager 分叉)?

How to pass a variable from a child process (fork by Parallel::ForkManager)?

我的查询:

在下面的代码中,我试图将打印 $commandoutput[0] 转移或传递到即将到来的 subroutine.i 尝试转移以传递 it.But 我失败了 it.Can请你帮我找到正确的方法?

代码:

my $max_forks = 4;

#createThreads();
my %commandData;
my @arr = (
   'bhappy',  'bload -m all -l -res CPUSTEAL',
   'bqueues', 'bjobs -u all -l -hfreq 101'
);

#print @arr;
my $fork = new Parallel::ForkManager($max_forks);
$fork->run_on_start(
   sub {
      my $pid = shift;
   }
);
$fork->run_on_finish(
   sub {
      my ( $pid, $exit, $ident, $signal, $core ) = @_;
      if ($core) {
         print "PID $pid core dumped.\n";
      }
      else { }
   }
);
my @Commandoutput;
my $commandposition = 0;
for my $command (@arr) {
   $fork->start and next;
   my @var = split( " ", $command );
   $commandoutput[$commandposition] = `$command`;
   $commandposition++;
   $line = $commandoutput[0];

# print $line;
   $fork->finish;
}
$fork->wait_all_children;

#print Dumper(\%commandData);
print $commandoutput[0];

这里我曾尝试将打印 $commandoutput[0] 存储在 subroutine.I 内的变量中 gated here 如何将变量从外部传递到子例程内部。

sub gen_help_data
{
  my $lines=shift;
  print $lines;
}

我认为您误解了叉子的作用。当您成功分叉时,您正在创建一个子流程,独立于您开始的流程,以继续工作。因为它是一个单独的进程,所以它有自己的内存、变量等,尽管其中一些是从父进程开始的副本。

因此您在每个子进程中设置 $commandoutput[0],但是当该子进程终止时,其 @commandoutput.

副本的内容也会终止

您可以 运行 每个命令连续执行,或者您可以使用线程(这会带来许多其他问题 - 您的代码需要进行一些重大的重新设计才能与线程一起工作),或者您可以使用事件(POE、AnyEvent 等,这将是另一个重大的重新设计)。或者您可以 运行 每个命令及其输出放入临时文件,然后,一旦所有子项都完成,读取每个文件并继续。这也有问题,但通常比其他问题少。

startfinish 之间的代码在 单独的进程中运行 并且 child 和 parent 无法写入彼此的变量(即使具有相同的名称)。 Forking创建了一个独立的进程,拥有自己的内存和数据。为了在这些进程之间传递数据,我们需要使用"Inter-Process-Communication"(IPC)机制。

此模块确实提供了一种现成且简单的方法来将数据从 child 传回 parent。 请参阅文档中的 Retrieving data structures from child processes

您首先需要向 finish 提供对 child 想要 return 的数据结构的引用。在你的情况下,你想要 return 一个标量 $commandoutput[0] 所以

$fork->finish(0, $commandoutput[0]);

然后在回调中找到此引用作为最后一个、第六个参数。您的代码遗漏的那个。所以在回调中你需要

my %ret_data;  # to store data from different child processes

$pm->run_on_finish( 
    sub { 
        my ($pid, $exit, $ident, $signal, $core, $dataref) = @_; 
        $ret_data{$pid} = $dataref;
    }
);

这里的$dataref$commandoutput[0],作为进程id的键的值存储在%ret_data中。所以在 foreach 完成后你可以在 %ret_data

中找到所有数据
foreach my $pid (keys %ret_data) {
    say "Data from $pid => ${$ret_data{$pid}}";
}

这里我们取消引用 $ret_data{$pid} 作为标量引用,因为您的代码 return 就是这样。

请注意,数据是通过写出文件传递的,如果有很多事情发生,这可能会很慢。


这是一个完整的示例,其中每个 child return 都是一个数组引用,将其传递给 finish,然后在回调中检索。另一个例子见 .

use warnings;
use strict;
use feature 'say';

use Parallel::ForkManager;    
my $pm = Parallel::ForkManager->new(4); 

my %ret_data;

$pm->run_on_finish( sub { 
    my ($pid, $exit, $ident, $signal, $core, $dataref) = @_; 
    $ret_data{$pid} = $dataref;
});

foreach my $i (1..8)
{
    $pm->start and next;
    my $ref = run_job($i);
    $pm->finish(0, $ref);
}
$pm->wait_all_children;

foreach my $pid (keys %ret_data) {
    say "$pid returned: @{$ret_data{$pid}}";
}

sub run_job { 
    my ($i) = @_;
    return [ 1..$i ];  # make up return data: arrayref with list 1..$i
}

版画

15037 returned: 1 2 3 4 5 6 7
15031 returned: 1 2
15033 returned: 1 2 3 4
15036 returned: 1 2 3 4 5 6
15035 returned: 1 2 3 4 5
15038 returned: 1 2 3 4 5 6 7 8
15032 returned: 1 2 3
15030 returned: 1

在现代系统中,出于性能原因,在分叉新进程时尽可能少地复制数据。因此 child "inherits" 通过分叉产生的变量实际上并不是副本,因此 child 实际上读取了 parent 分叉时存在的变量。

但是,child 在内存中写入 的任何数据对于 parent 是不可访问的(并且 parent 在分叉后写入的是child)不知道。如果该数据在分叉 时从 parent 写入变量 "inherited",则发生 数据复制,以便 child 的新数据是独立的。

数据的管理方式肯定存在细微差别和复杂性,即使 child 中的数据发生变化,也只有 apparent 维护了一些指针。我猜这主要是为了简化数据管理,减少复制;数据管理的粒度似乎比 "variable" 级别细得多。

但这些是实现细节,一般来说 child 和 parent 不能互相查看对方的数据。