在Mojolicious中同时获取数据



我正在尝试并行运行多个子例程(从外部系统获取数据)。为了模拟,我在下面的示例中使用了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&mult2)将运行所需的时间减少到2秒(而不是3秒)。

这个线程使用了一个似乎与我的情况无关的Mojo::IOLoop->timer?如果是这样,我不知道如何使用它。谢谢!

为了避免长时间的等待,您可以使用Mojolicious非阻塞操作。不要运行对外部系统的同步请求,而是使用非阻塞方法,而是在完成后运行一些回调。例如,为了避免sleep,我们将使用Mojo::IOLoop->timer(...)

下面是使用非阻塞操作的代码变体,并使用 Promise 对函数进行正确排序:

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 secondsn");
})
->wait;
};
app->start;

这比你的代码复杂得多,但在两秒内完成而不是三秒,并且不会阻止同时发生的其他请求。因此,您可以一次请求此路由三十次,并在两秒后获得三十个响应!

请注意,Promise->all返回一个包含所有等待承诺值的承诺,但将每个承诺的值放入数组 ref 中,因此我们需要解压缩这些值以获取实际值。

如果无法使用非阻塞操作,则可以使用Mojo::IOLoop->subprocess(...)在子进程中运行阻塞代码。您仍然可以通过承诺协调数据流。例如,有关示例,请参阅上面的mult2函数。

并发与并行

为了使两个事件同时发生(并行),你需要多个处理单元。

例如,使用单个 CPU,您只能在任何时间执行单个数学运算,因此无论代码中的并发主题如何,它们都必须串联运行。

非数学运算,如输入/输出(例如网络,HDD)可以并行发生,因为这些操作在大多数情况下独立于单个CPU(我将不解释多核系统,因为一般来说Perl没有针对它们的使用进行优化)。

Hypnotoad,Mojolicious的生产Web服务器,依赖于非阻塞IO的正确实现。因此,他们提供了一个非阻塞用户代理作为控制器的一部分。

$controller->ua->get(
$the_url,
sub {
my ( $ua, $tx ) = @_;
if ( my $result = $tx->success ) {
# do stuff with the result
}
else {
# throw error
}
}
);

您可以在此处实现Mojo::P romise以改善代码流程。

如果可能的话,我建议在从"外部系统"获取数据时实现非阻塞 UA。如果你发现你的催眠蟾蜍工作进程阻塞时间过长(5秒),它们很可能会被杀死并被替换,这可能会破坏你的系统。

相关内容

  • 没有找到相关文章

最新更新