【问题标题】:Fetching data simultaneously in Mojolicious在 Mojolicious 中同时获取数据
【发布时间】:2018-04-08 07:22:56
【问题描述】:

我正在尝试并行运行多个子例程(从外部系统获取数据)。为了模拟,我在下面的示例中使用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 秒)。

这个thread 使用了Mojo::IOLoop-&gt;timer,这在我的情况下似乎不相关?如果是这样,我不知道如何使用它。谢谢!

【问题讨论】:

标签: multithreading perl web-services parallel-processing mojolicious


【解决方案1】:

为避免长时间等待,您可以使用 Mojolicious 非阻塞操作。不要对外部系统运行同步请求,而是使用非阻塞方法,在完成时运行一些回调。例如。为了避免sleep,我们将使用Mojo::IOLoop-&gt;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-&gt;all 返回一个包含所有等待承诺的值的承诺,但会将每个承诺的值放入一个数组 ref 中,因此我们需要将它们解包以获取实际值。

如果你不能使用非阻塞操作,你可以在子进程中运行阻塞代码,使用Mojo::IOLoop-&gt;subprocess(...)。您仍然可以通过 Promise 协调数据流。例如。示例见上面的mult2 函数。

【讨论】:

  • 谢谢。我需要进一步研究你的代码。我的代码中当然没有sleep。我只是想说明这一点(这需要时间)。我需要在没有Mojo::IOLoop-&gt;timer 的情况下尝试您的示例代码。我也会调查Mojo::IOLoop-&gt;subprocess(...)
  • 描述如何等待子进程、协调何时应用电源调用会很有用。
  • @DavidO 我更新了一个子流程示例。你可以继续使用 Promise 以正确的顺序运行代码。
  • @amon :谢谢。鉴于您的示例,我采取了将业务逻辑代码与控制器分离的额外步骤,以便慢速潜艇无需修改即可生存,同时仍利用 Mojo::IOLoop::subprocess 和 ::promise。见gist.github.com/daoswald/17c1c37de52c700d794dc867cae9ca49
  • @DavidO 在可能的情况下,最好完全异步而不是尝试并行化同步操作。例如。分叉子流程相对昂贵,并且需要您序列化返回值。某些资源(通常是数据库连接)不能在子进程中重用。在您的情况下,您意外地仍在同步调用 power() 函数,这会阻止所有请求!所以我认为你的方法作为完全异步代码的迁移策略是可行的,但它还没有完全获得全部好处。
【解决方案2】:

并发与并行

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

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

输入/输出等非数学运算(例如网络、硬盘驱动器)可以并行方式发生,因为这些运算在很大程度上独立于您的单个 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 秒),它们很可能会被杀死并被替换,这可能会破坏您的系统。

【讨论】:

  • 非常感谢。我同意你的看法,我不会进行数学运算,而是从外部系统获取数据。您能提供一个工作示例吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-23
  • 2019-11-21
  • 2020-08-28
  • 1970-01-01
  • 2019-03-05
  • 2017-12-06
  • 2021-04-23
相关资源
最近更新 更多