在 Mojolicious 中同时获取数据

Fetching data simultaneously in 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;

换句话说,我想通过 运行ning (add1 & mult2) 并行。

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

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

这是您的代码的变体,它使用 non-blocking 操作,并使用 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;

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

请注意,Promise->all returns 一个具有所有等待承诺值的承诺,但将每个承诺的值放入一个数组引用中,因此我们需要解压缩它们以获取实际值。

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

并发与并行

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

例如,对于单个 CPU,您在任何时候只能执行一个数学运算,因此无论 并发 代码中的主题。

Non-mathematical 操作,例如 input/output(例如网络、硬盘驱动器)可以以 并行 方式发生,因为这些在大多数情况下独立于您的单个 CPU(我将不解释 multi-core 系统,因为一般来说 Perl 并未针对它们的使用进行优化)。

Mojolicious 的产品 web-server Hypnotoad 依赖于 non-blocking IO 的正确实施。因此,他们提供了 non-blocking user-agent 作为控制器的一部分。

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

您可以在此处实施 Mojo::Promise 以改进您的代码流程。

如果可能,我建议在从 "external systems" 获取数据时实施 non-blocking UA。如果您发现您的 Hypnotoad 工作进程阻塞时间过长(5 秒),它们很可能会被杀死并被替换,这可能会破坏您的系统。