分割内部循环,防止迭代器使用Parallel :: Prefork递增

时间:2013-07-22 17:13:19

标签: perl iterator fork

我有一些代码,我希望输出为1和6,但它会无限输出1个。

use v5.10;
use Parallel::Prefork;
use List::MoreUtils qw( natatime );
use POSIX qw( ceil );

my $forks = 2;

my @numbers       = (1..10);
my $chunk_size    = ceil((scalar @numbers) / $forks);
my $game_iterator = natatime $chunk_size, @numbers;
my $fm            = Parallel::Prefork->new({ max_workers => $forks });

while ($fm->signal_received ne 'TERM') {
  while( my @numbers_chunk = $game_iterator->() ) { 
    $fm->start(sub {
        say $numbers_chunk[0];
    });
  }
}

$fm->wait_all_children;

# bash-4.2$ perl test.pl
# 1
# 1
# 1
# 1
# 1
# etc

上面的脚本将10个数字的数组拆分为$ fork数组(2),并且应该将每个数组传递给它们自己的fork进行处理。

如果仅用$fm->start(sub {say $numbers_chunk[0];});替换say $numbers_chunk[0];,则会显示正确的结果。 Parallel :: ForkManager也输出正确的结果(按照概要),所以我不知道我是做错了什么,或者这是模块中的错误。

输出预期结果的ForkManager脚本:

use v5.10;
use Parallel::ForkManager;
use List::MoreUtils qw( natatime );
use POSIX qw( ceil );

my $forks = 2;

my @numbers       = (1..10);
my $chunk_size    = ceil((scalar @numbers) / $forks);
my $game_iterator = natatime $chunk_size, @numbers;
my $fm            = Parallel::ForkManager->new($forks );

while( my @numbers_chunk = $game_iterator->() ) { 
  $fm->start and next;
  say $numbers_chunk[0];
  $fm->finish;
}

$fm->wait_all_children;


# bash-4.2$ perl test.pl
# 1
# 6

2 个答案:

答案 0 :(得分:2)

与文档相反,Parallel :: Prefork与Parallel :: ForkManager的非常不同。它被设计用于像Web服务器这样的东西,它可以加载一次配置,然后生成相同的子节点直到它被信号关闭。

因此,start会根据需要继续创建子项,并且在发出终止整个过程的信号之前不会返回。

也就是说,可以使用before_fork使P :: Prefork像胖版P :: ForkManager一样工作。

use strict;
use warnings;
use v5.10;

use List::MoreUtils   qw( natatime );
use Parallel::Prefork qw( );
use POSIX             qw( ceil );

my $forks = 2;

my @numbers       = (1..10);
my $chunk_size    = ceil(@numbers / $forks);
my $game_iterator = natatime($chunk_size, @numbers);

my @numbers_chunk;

my $fm = Parallel::Prefork->new({
   max_workers => $forks,
   trap_signals => { TERM => 'TERM' },
   before_fork => sub {
      @numbers_chunk = $game_iterator->()
         or kill(TERM => $$);
   },
});

$fm->start(sub {
   say $numbers_chunk[0];
});

$fm->wait_all_children();

但为什么不使用Parallel :: ForkManager而不是强制Parallel :: Prefork来模拟它呢?

答案 1 :(得分:1)

Parallel::Prefork适用于不需要来自父进程的数据的独立无状态,可重新启动的工作进程。该模块没有提供将数据线程化到回调的工具,这使得设置通信通道 - 例如传递数字块 - 很难。

与下面直接调用fork的简单程序相比,该模块似乎没有给你买任何东西。

#! /usr/bin/env perl

use strict;
use warnings;

use v5.10;
use List::MoreUtils qw( natatime );
use POSIX qw( ceil WNOHANG );

my $forks = 2;

my @numbers       = (1 .. 10);
my $chunk_size    = ceil(scalar @numbers / $forks);
my $game_iterator = natatime $chunk_size, @numbers;

for (1 .. $forks) {
  if (my @numbers_chunk = $game_iterator->()) {
    unless (fork // die "$0: fork: $!") {
      say $numbers_chunk[0];
      exit 0;
    }
  }
}

# wait for all child processes
my $pid;
do { $pid = waitpid -1, WNOHANG } while $pid > 0;

您可以使用System V IPC,例如来解决Parallel :: Prefork的设计约束,并使用如下代码中的消息队列。

#! /usr/bin/env perl

use strict;
use warnings;

use Parallel::Prefork;
use List::MoreUtils qw( natatime );
use POSIX qw( ceil );
use IPC::SysV qw(IPC_NOWAIT IPC_PRIVATE S_IRUSR S_IWUSR);
use IPC::Msg;
use Errno qw( ENOMSG );

my $forks = 3;

my @numbers       = (1 .. 20);
my $chunk_size    = ceil((scalar @numbers) / $forks);
my $game_iterator = natatime $chunk_size, @numbers;
my $fm            = Parallel::Prefork->new({ max_workers => $forks });

my $maxsize = 0;
my $msg = new IPC::Msg(IPC_PRIVATE, S_IRUSR | S_IWUSR);
while (my @numbers_chunk = $game_iterator->()) {
  my $chunk = join " ", @numbers_chunk;
  $msg->snd(1, $chunk) or die "$0: msgsnd: $!";
  $maxsize = length $chunk if length $chunk > $maxsize;
}

my $ppid = $$;

while ($fm->signal_received ne 'TERM') {
  $fm->start(sub {
    my $ok = $msg->rcv(my $buf, $maxsize, 1, IPC_NOWAIT);
    if (!$ok) {
      if ($!{ENOMSG}) {
        sleep 1;  # XXX: poor man's synchronization
        kill TERM => $ppid or die "$0: kill: $!";
        return;
      }
      die "$0: msgrcv: $!";
    }
    print "[$$]: got '$buf'\n";
  });
}

$fm->wait_all_children;

此实现是可以通过的,因为所有进程都使用相同的全局消息队列对象。

示例输出:

[31198]: got '8 9 10 11 12 13 14'
[31197]: got '1 2 3 4 5 6 7'
[31200]: got '15 16 17 18 19 20'

正如上面的代码所示,你真的想要一个比Parallel :: Prefork提供的更适合你的问题的抽象。