我有一些代码,我希望输出为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
答案 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提供的更适合你的问题的抽象。