如何等待子进程在父进程中设置变量?

时间:2016-08-22 04:09:24

标签: perl parallel-processing fork ipc child-process

 use Parallel::ForkManager;    
 my $number_running = 0;
 my $pm = new Parallel::ForkManager(30); 
 $pm->run_on_start( sub { ++$number_running; } );
 $pm->run_on_finish( sub { --$number_running; } );
 for (my $i=0; $i<=100; $i++)
 {
     if ($number_running == 5) { while ($number_running > 0) {} }  # waits forever
     $pm->start and next;
     print $i;
     $pm->finish;
 }

上面的代码使用Parallel::ForkManager来使用并行进程在for循环中执行代码。它正在计算正在运行的子进程数,并相应地设置$number_running变量。一旦有5个子进程在运行,我希望它等到0个子进程正在运行才能继续。

for循环中的第一行旨在实现此目的,但它会在该行上永远等待。这就像子进程对变量所做的更改不适用于该行代码。我究竟做错了什么?注意:我知道wait_all_children,但我不想使用它。

1 个答案:

答案 0 :(得分:1)

回调run_on_start与每个新进程一起运行,计数器递增。但回调run_on_finish永远不会被触发,因此计数器永远不会减少。因此,一旦达到5,代码就会位于while循环中。请注意,父级和子级不能直接更改彼此的变量,而是单独的过程。

回调run_on_finish通常是在所有进程分叉后通过wait_all_children触发的。它的工作也完成了 当最大进程数运行且一个进程退出时。这是通过致电startwait_one_child调用on_finish完成的,见下文)。

或者,这可以通过调用reap_finished_children方法

随意完成
  

这是一项非阻塞调用,可以收集子级并执行回调,而不依赖于对startwait_all_children的调用。在不经常调用start的情况下使用它,但是您希望快速执行回调。

这解决了主要问题如何在个别孩子退出时进行沟通(在评论中明确说明),而不是wait_all_children

以下是如何使用它的示例,以便在子进程退出时正确运行回调。

use warnings;
use strict;
use feature 'say';
use Parallel::ForkManager;    
$| = 1;

my $total_to_process = 3;    
my $number_running   = 0;    
my @ds;

my $pm = new Parallel::ForkManager(30);

$pm->run_on_start( sub {
    ++$number_running;
    say "Started $_[0], total: $number_running";
});
$pm->run_on_finish( sub {
    --$number_running;
    my ($pid, $code, $iden, $sig, $dump, $rdata) = @_;
    push @ds, "gone-$pid";
    say "Cleared $pid, ", ($rdata->[0] // ''), ($code ? " exit $code" : '');
});

foreach my $i (1 .. $total_to_process)
{
    $pm->start and next;
    run_job($i);
    $pm->finish(10*$i, [ "kid #$i" ]);
}
say "Running: ", map { "$_ " } $pm->running_procs;  # pid's of children

# Reap right as each process exits, retrieve and print info
my $curr = $pm->running_procs;
while ($pm->running_procs) 
{
    $pm->reap_finished_children;    # may be fewer now
    if ($pm->running_procs < $curr) {
        $curr = $pm->running_procs;
        say "Remains: $number_running. Data: @ds";
    }
    sleep 1;  # or use Time::HiRes::sleep 0.1;
}

sub run_job {
    my ($num) = @_;
    my $sleep_time = ($num == 1) ? 1 : ($num == 2 ? 10 : 20);
    sleep $sleep_time;
    say "\tKid #$num slept for $sleep_time, exiting";
}

使用此方法相当于在waitpid -1, POSIX::WNOHANG之后的循环中调用fork。这比使用max {30个进程更少分配,以便更容易地查看输出,并证明回调在子进程退出时运行正常。更改这些数字以查看其完整操作。

大量代码用于诊断。我们退出10*$i以跟踪输出中的子项。出于相同目的,匿名数组[...]中返回的数据是描述性字符串。只要reap_finished_children完成,$number_running就会在回调中减少。这就是我们需要$curr变量(再次用于诊断)的原因。

打印

start: Started 4656, running: 1
start: Started 4657, running: 2
start: Started 4658, running: 3
Running: 4656 4658 4657 
        Kid #1 slept for 1, exiting
Cleared 4656, kid #1 exit 10
Remains: 2. Data: gone-4656
        Kid #2 slept for 10, exiting
Cleared 4657, kid #2 exit 20
Remains: 1. Data: gone-4656 gone-4657
        Kid #3 slept for 20, exiting
Cleared 4658, kid #3 exit 30
Remains: 0. Data: gone-4656 gone-4657 gone-4658

直接问题是如何在开始新批次之前等待整批完成。这可以通过wait_for_available_procs($n)

直接完成
  

等到$n个可用的进程位置可用。如果未指定$n,则默认为 1

如果$MAX用于$n,则只有整批完成后才能使用多个广告位。用于$n的内容也可以在运行时决定。

模块操作的一些细节

当一个孩子退出时,SIGCHLD信号被发送给父母,为了知道孩子已经离开(并且首先避免僵尸),它必须抓住它。这是通过在代码或wait处理程序中使用waitpidSIGCHLD来完成的(但仅限于一个地方)。请参阅forkSignals in perlipcwaitpidwait

我们从P::FM's source看到这是在wait_one_child(通过_waitpid sub)

完成的
sub wait_one_child { my ($s,$par)=@_;  
  my $kid;
  while (1) {
    $kid = $s->_waitpid(-1,$par||=0);
    last if $kid == 0 || $kid == -1; # AS 5.6/Win32 returns negative PIDs
    redo if !exists $s->{processes}->{$kid};
    my $id = delete $s->{processes}->{$kid};
    $s->on_finish( $kid, $? >> 8 , $id, $? & 0x7f, $? & 0x80 ? 1 : 0);
    last;
  }
  $kid;
};  

wait_all_children

中使用
sub wait_all_children { my ($s)=@_;
  while (keys %{ $s->{processes} }) {
    $s->on_wait;
    $s->wait_one_child(defined $s->{on_wait_period} ? &WNOHANG : undef);
  };
}

上面使用的方法reap_finished_children是此方法的同义词。

获取信号的方法wait_one_childstart用于在填充最大进程数并退出一个进程时获取子进程。这是模块何时知道何时可以启动另一个进程并遵守其最大值。 (它也被一些等待进程的其他例程使用。 )。这是run_on_finish

触发$s->on_finish( $kid, ... )时的情况
sub on_finish {
  my ($s,$pid,@par)=@_;
  my $code=$s->{on_finish}->{$pid} || $s->{on_finish}->{0} or return 0;
  $code->($pid,@par);
};

回调位于coderef $code中,从对象的on_finish键中检索,该键本身在子run_on_finish中设置。一旦该子运行,这就是回调的设置方式。

用户使用的方法是wait_all_childrenreap_finished_children

由于在发布的代码中没有使用这个,$number_running没有得到更新,因此while是一个无限循环。回想一下子进程无法直接更改父中的变量$number_running