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
,但我不想使用它。
答案 0 :(得分:1)
回调run_on_start
与每个新进程一起运行,计数器递增。但回调run_on_finish
永远不会被触发,因此计数器永远不会减少。因此,一旦达到5
,代码就会位于while
循环中。请注意,父级和子级不能直接更改彼此的变量,而是单独的过程。
回调run_on_finish
通常是在所有进程分叉后通过wait_all_children
触发的。它的工作也完成了
当最大进程数运行且一个进程退出时。这是通过致电start
(wait_one_child
调用on_finish
完成的,见下文)。
或者,这可以通过调用reap_finished_children方法
随意完成这是一项非阻塞调用,可以收集子级并执行回调,而不依赖于对
start
或wait_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
处理程序中使用waitpid
或SIGCHLD
来完成的(但仅限于一个地方)。请参阅fork,Signals in perlipc,waitpid和wait。
我们从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_child
由start
用于在填充最大进程数并退出一个进程时获取子进程。这是模块何时知道何时可以启动另一个进程并遵守其最大值。 (它也被一些等待进程的其他例程使用。
)。这是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_children
和reap_finished_children
。
由于在发布的代码中没有使用这个,$number_running
没有得到更新,因此while
是一个无限循环。回想一下子进程无法直接更改父中的变量$number_running
。