如何等待子进程在父进程中设置变量?
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
,但我不想使用它。
回调run_on_start
与每个新进程一起运行并且计数器递增。但回调run_on_finish
从不触发,所以计数器永远不会递减。因此,一旦它达到5
代码位于while
循环。请注意,父母和子女不能直接更改彼此的变量,作为单独的进程。
回调run_on_finish
通常在所有进程分叉后由wait_all_children
触发。当最大进程数量运行并且一个进程退出时,它的工作也完成了 。这通过致电wait_one_child
(其调用on_finish
,见下文)在start
中完成。
或者,这可以通过调用reap_finished_children
方法
这是一个非阻塞调用来获得孩子和执行回调独立呼叫到
start
或wait_all_children
来完成。在不经常调用start
的情况下使用它,但您希望快速执行回调。
这解决的主要关注如何沟通个别孩子退出(如在评论澄清),而不是由wait_all_children
。
下面是一个如何使用它的示例,以便在小孩退出时回调正确运行。
使用这种方法相当于后fork
-ing调用waitpid -1, POSIX::WNOHANG
在一个循环。这比最大(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 exited with 10 Remains: 2. Data: gone-4656 Kid #2 slept for 10, exiting Cleared 4657, kid #2 exited with 20 Remains: 1. Data: gone-4656 gone-4657 Kid #3 slept for 20, exiting Cleared 4658, kid #3 exited with 30 Remains: 0. Data: gone-4656 gone-4657 gone-4658
的直接问题是如何等待整批来开始新的之前完成。这可以直接通过wait_for_available_procs($n)
等到
$n
可用的进程插槽可用。如果未给出$n
,则默认为。
如果$MAX
用于$n
,那么许多插槽只有在整批完成后才可用。在运行时也可以决定使用什么$n
。
当一个孩子离开SIGCHLD
信号被发送到父,它必须为了赶上模块操作的一些细节就知道孩子走了(并避免僵尸,首先)。这是通过在代码中或在SIGCHLD
处理程序中使用wait
或waitpid
(但仅在一个地方)完成的。参见fork
,Signals in perlipc
,waitpid
和wait
。
我们从P::FM's source
看到,这是在完成wait_one_child
(经由_waitpid
子)
sub wait_one_child { my ($s,$par)[email protected]_; 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)[email protected]_; while (keys %{ $s->{processes} }) { $s->on_wait; $s->wait_one_child(defined $s->{on_wait_period} ? &WNOHANG : undef); }; }
的方法,使用上面使用的0是这种方法的同义词。
获取信号的方法wait_one_child
被start
用于在最大进程数已满并且有一个退出时收获子进程。这是模块如何知道何时可以启动另一个进程并尊重其最大值的方式。 (它也被一些等待进程的其他例程使用, )。这是当run_on_finish
被触发,通过$s->on_finish($kid, ...)
sub on_finish { my ($s,$pid,@par)[email protected]_; 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
不能通过子进程直接更改。
每次子进程结束时执行'run_on_finish'。 '$ number_running'由子进程更新。我自己测试了这个。我不想使用'wait_for_children'。我想知道如何等待'$ number_running'被子进程减少到零。 – CJ7
@ CJ7 1.孩子与柜台无关。子代码就是我们在'start'后输入的内容。 2.你想为儿童“等待”(或“waitpid”),这是分叉的第一条规则。 3.通过信号通知孩子退出的信息,为了真正知道孩子退出,必须抓住(并处理)这些信号。另一种方式是让孩子“回到”管道(或发送一个“SIGUSR”信号)以表明它已经完成,但是我们输入孩子代码,事实并非如此。 4.我终于看到了源代码,这个“等待”确实是在'wait_all_children'中完成的。我会更新我的帖子。 – zdim
@ CJ7'while'显然是无限的,这只能是因为'$ number_running'没有得到更新,即使子进程在创建后很快退出。这只能是因为回调没有运行。当我将'wait_all_children'注释掉时,我看到它的确认 - 只有在所有进程分叉之后才开始打印“清除PID”行。 – zdim
你不应该在这样的空闲循环中旋转。您的父进程将耗尽所有CPU时间,并无休止地测试'$ number_running'的值。 – Borodin
如何等待'$ number_running'由子进程递减至零? – CJ7
@ CJ7子进程不会影响在父子进程中使用的$ number_running,并且父进程不能写入彼此的变量。父母必须为自己减少这个变量。请参阅我的答案中添加的解释。 – zdim