在Mojolicious

时间:2018-04-08 07:22:57

标签: multithreading perl web-services parallel-processing 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&amp; mult2)来减少耗时2秒(而不是3秒)的时间。

thread使用的Mojo::IOLoop->timer在我的案例中似乎不相关?如果是这样,我不知道如何使用它。谢谢!

2 个答案:

答案 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秒),则可能会被杀死并更换,这可能会破坏您的系统。