我正在尝试并行运行多个子程序(从外部系统获取数据)。为了模拟,我在下面的示例中使用了sleep
。我的问题是:如何在Mojolicious实现这一目标?
#!/usr/bin/perl
use Mojolicious::Lite;
use Benchmark qw(:hireswallclock);
sub add1 { my $a = shift; sleep 1; return $a+1; }
sub mult2 { my $b = shift; sleep 1; return $b*2; }
sub power { my ($x, $y) = @_; sleep 1; return $x ** $y; }
any '/' => sub {
my ( $self ) = @_;
my $n = int(rand(5));
my $t0 = Benchmark->new;
my $x = mult2($n); # Need to run in parallel
my $y = add1($n); # Need to run in parallel
my $z = power($x,$y);
my $t1 = Benchmark->new;
my $t = timediff($t1,$t0)->real();
$self->render(text => "n=$n, x=$x, y=$y, z=$z;<br>T=$t seconds");
};
app->start;
换句话说,我想通过并行运行(add1
&amp; mult2
)来减少耗时2秒(而不是3秒)的时间。
此thread使用的Mojo::IOLoop->timer
在我的案例中似乎不相关?如果是这样,我不知道如何使用它。谢谢!
答案 0 :(得分:7)
为避免漫长的等待时间,您可以使用Mojolicious非阻塞操作。而不是向外部系统运行同步请求,而是使用非阻塞方法,而不是在完成时运行一些回调。例如。要避免使用sleep
,我们会使用Mojo::IOLoop->timer(...)
。
以下是使用非阻塞操作的代码变体,并使用Promises正确排序函数:
use Mojolicious::Lite;
use Benchmark qw(:hireswallclock);
# example using non-blocking APIs
sub add1 {
my ($a) = @_;
my $promise = Mojo::Promise->new;
Mojo::IOLoop->timer(1 => sub { $promise->resolve($a + 1) });
return $promise;
}
# example using blocking APIs in a subprocess
sub mult2 {
my ($b) = @_;
my $promise = Mojo::Promise->new;
Mojo::IOLoop->subprocess(
sub { # first callback is executed in subprocess
sleep 1;
return $b * 2;
},
sub { # second callback resolves promise with subprocess result
my ($self, $err, @result) = @_;
return $promise->reject($err) if $err;
$promise->resolve(@result);
},
);
return $promise;
}
sub power {
my ($x, $y) = @_;
my $result = Mojo::Promise->new;
Mojo::IOLoop->timer(1 => sub { $result->resolve($x ** $y) });
return $result;
}
any '/' => sub {
my ( $self ) = @_;
my $n = int(rand(5));
my $t0 = Benchmark->new;
my $x_promise = mult2($n);
my $y_promise = add1($n);
my $z_promise = Mojo::Promise->all($x_promise, $y_promise)
->then(sub {
my ($x, $y) = map { $_->[0] } @_;
return power($x, $y);
});
Mojo::Promise->all($x_promise, $y_promise, $z_promise)
->then(sub {
my ($x, $y, $z) = map { $_->[0] } @_;
my $t1 = Benchmark->new;
my $t = timediff($t1, $t0)->real();
$self->render(text => "n=$n, x=$x, y=$y, z=$z;\nT=$t seconds\n");
})
->wait;
};
app->start;
这比您的代码复杂得多,但是在两秒内完成而不是三秒,并且不会阻止同时发生的其他请求。所以你可以一次要求这条路线三十次,并在两秒钟后得到三十条回应!
请注意Promise->all
返回一个带有所有等待的promises值的promise,但是将每个promise的值放入数组ref中,所以我们需要解压缩它们以获得实际值。
如果您不能使用非阻塞操作,则可以使用Mojo::IOLoop->subprocess(...)
在子进程中运行阻止代码。您仍然可以通过promises协调数据流。例如。有关示例,请参阅上面的mult2
函数。
答案 1 :(得分:1)
并发与并行
为了同时(并行)发生两个事件,您需要多个处理单元。
例如,使用单个CPU,您只能在任何时间执行单个数学运算,因此无论代码中的并发主题如何,它们都必须连续运行。< / p>
非数学运算,例如输入/输出(例如网络,HDD)可以以并行方式发生,因为这些操作在很大程度上独立于您的单个CPU(我将留下多个一般来说,Perl没有针对它们的使用进行优化,因此核心系统不在解释之中。
Hypnotoad,Mojolicious'生产网络服务器,依赖于非阻塞IO的正确实现。因此,他们提供了一个非阻塞用户代理作为控制器的一部分。
$controller->ua->get(
$the_url,
sub {
my ( $ua, $tx ) = @_;
if ( my $result = $tx->success ) {
# do stuff with the result
}
else {
# throw error
}
}
);
您可以在此处实现Mojo :: Promise以改善代码流程。
如果可能,我建议在从“外部系统”获取数据时实现非阻塞UA。如果您发现您的Hypnotoad工作进程阻塞时间过长(5秒),则可能会被杀死并更换,这可能会破坏您的系统。