2016-08-22 138 views
4
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

你不应该在这样的空闲循环中旋转。您的父进程将耗尽所有CPU时间,并无休止地测试'$ number_running'的值。 – Borodin

+0

如何等待'$ number_running'由子进程递减至零? – CJ7

+0

@ CJ7子进程不会影响在父子进程中使用的$ number_running,并且父进程不能写入彼此的变量。父母必须为自己减少这个变量。请参阅我的答案中添加的解释。 – zdim

回答

1

回调run_on_start与每个新进程一起运行并且计数器递增。但回调run_on_finish从不触发,所以计数器永远不会递减。因此,一旦它达到5代码位于while循环。请注意,父母和子女不能直接更改彼此的变量,作为单独的进程。

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

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

这是一个非阻塞调用来获得孩子和执行回调独立呼叫到startwait_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处理程序中使用waitwaitpid(但仅在一个地方)完成的。参见fork,Signals in perlipc,waitpidwait

我们从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_childstart用于在最大进程数已满并且有一个退出时收获子进程。这是模块如何知道何时可以启动另一个进程并尊重其最大值的方式。 (它也被一些等待进程的其他例程使用, )。这是当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_childrenreap_finished_children

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

+0

每次子进程结束时执行'run_on_finish'。 '$ number_running'由子进程更新。我自己测试了这个。我不想使用'wait_for_children'。我想知道如何等待'$ number_running'被子进程减少到零。 – CJ7

+0

@ CJ7 1.孩子与柜台无关。子代码就是我们在'start'后输入的内容。 2.你想为儿童“等待”(或“waitpid”),这是分叉的第一条规则。 3.通过信号通知孩子退出的信息,为了真正知道孩子退出,必须抓住(并处理)这些信号。另一种方式是让孩子“回到”管道(或发送一个“SIGUSR”信号)以表明它已经完成,但是我们输入孩子代码,事实并非如此。 4.我终于看到了源代码,这个“等待”确实是在'wait_all_children'中完成的。我会更新我的帖子。 – zdim

+0

@ CJ7'while'显然是无限的,这只能是因为'$ number_running'没有得到更新,即使子进程在创建后很快退出。这只能是因为回调没有运行。当我将'wait_all_children'注释掉时,我看到它的确认 - 只有在所有进程分叉之后才开始打印“清除PID”行。 – zdim