You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Mojolicious中并行执行多子程序以获取外部数据?

在Mojolicious中并行运行耗时子程序的实现方案

嘿,刚好碰到过类似需求!Mojolicious本身就提供了完善的异步/并行处理工具,完全可以解决你这种需要同时跑多个耗时任务的场景——毕竟串行跑三个1秒的sleep要3秒,并行的话1秒就能搞定,效率差很多。

下面给你两种常用的实现方式,都是基于Mojolicious原生工具,不用额外装模块:

方法一:使用 Mojo::IOLoop::Subprocess 实现进程级并行

Mojo::IOLoop::Subprocess 可以把耗时任务放到独立子进程中执行,不会阻塞主事件循环,适合CPU密集或IO密集的同步任务(比如你的sleep模拟操作)。

修改后的完整代码如下:

#!/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 = shift;
    my $n = int(rand(5));
    my $t0 = Benchmark->new;

    # 存储三个任务的结果
    my (%results, $completed);

    # 任务1:执行add1
    Mojo::IOLoop::Subprocess->new->run(
        sub { add1($n) },
        sub {
            my ($subprocess, $err, $res) = @_;
            $results{add1} = $res;
            _check_all_done($self, \%results, $t0, $n) if ++$completed == 3;
        }
    );

    # 任务2:执行mult2
    Mojo::IOLoop::Subprocess->new->run(
        sub { mult2($n) },
        sub {
            my ($subprocess, $err, $res) = @_;
            $results{mult2} = $res;
            _check_all_done($self, \%results, $t0, $n) if ++$completed == 3;
        }
    );

    # 任务3:执行power
    Mojo::IOLoop::Subprocess->new->run(
        sub { power($n, 2) },
        sub {
            my ($subprocess, $err, $res) = @_;
            $results{power} = $res;
            _check_all_done($self, \%results, $t0, $n) if ++$completed == 3;
        }
    );
};

# 辅助函数:所有任务完成后返回结果
sub _check_all_done {
    my ($self, $results, $t0, $n) = @_;
    my $t1 = Benchmark->new;
    my $elapsed = timediff($t1, $t0)->real;

    $self->render(json => {
        input    => $n,
        results  => $results,
        elapsed  => sprintf("%.2f秒", $elapsed)
    });
}

app->start;

代码说明:

  • 每个run方法分为两个回调:第一个是要在子进程执行的任务(你的耗时子程序),第二个是任务完成后的处理逻辑;
  • 用$completed计数器跟踪任务完成数量,全部完成后调用辅助函数返回结果;
  • 子进程和主进程是隔离的,所以任务里的变量要通过参数传递,结果通过回调返回。

方法二:使用 Mojo::Promise 实现异步并行(更简洁)

Mojolicious的Promise API(Mojo::Promise)可以让异步代码更易读,适合并行多个任务的场景:

#!/usr/bin/perl
use Mojolicious::Lite;
use Benchmark qw(:hireswallclock);
use Mojo::Promise;

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;
}

# 把同步子程序包装成Promise
sub promise_add1 {
    my $n = shift;
    return Mojo::Promise->new(sub {
        my $promise = shift;
        # 用subprocess避免阻塞事件循环
        Mojo::IOLoop::Subprocess->new->run(
            sub { add1($n) },
            sub {
                my ($sub, $err, $res) = @_;
                $err ? $promise->reject($err) : $promise->resolve($res);
            }
        );
    });
}

sub promise_mult2 {
    my $n = shift;
    return Mojo::Promise->new(sub {
        my $promise = shift;
        Mojo::IOLoop::Subprocess->new->run(
            sub { mult2($n) },
            sub {
                my ($sub, $err, $res) = @_;
                $err ? $promise->reject($err) : $promise->resolve($res);
            }
        );
    });
}

sub promise_power {
    my $n = shift;
    return Mojo::Promise->new(sub {
        my $promise = shift;
        Mojo::IOLoop::Subprocess->new->run(
            sub { power($n, 2) },
            sub {
                my ($sub, $err, $res) = @_;
                $err ? $promise->reject($err) : $promise->resolve($res);
            }
        );
    });
}

any '/' => sub {
    my $self = shift;
    my $n = int(rand(5));
    my $t0 = Benchmark->new;

    # 并行执行三个Promise
    Mojo::Promise->all(
        promise_add1($n),
        promise_mult2($n),
        promise_power($n)
    )->then(sub {
        my ($add_res, $mult_res, $power_res) = @_;
        my $t1 = Benchmark->new;
        my $elapsed = timediff($t1, $t0)->real;

        $self->render(json => {
            input    => $n,
            results  => {
                add1  => $add_res->[0],
                mult2 => $mult_res->[0],
                power => $power_res->[0]
            },
            elapsed  => sprintf("%.2f秒", $elapsed)
        });
    })->catch(sub {
        my $err = shift;
        $self->render(json => { error => $err }, status => 500);
    });
};

app->start;

代码说明:

  • 先把每个同步子程序包装成返回Promise的函数,内部还是用Subprocess避免阻塞;
  • Mojo::Promise->all会等待所有Promise完成,然后进入then回调处理结果;
  • catch用来捕获任何任务的错误,容错性更好。

关键注意点

  • 不管用哪种方法,都不要直接在主事件循环里执行耗时同步任务(比如你的sleep),否则会阻塞整个应用;
  • 如果你的实际任务是调用外部HTTP接口,还可以用Mojo::UserAgent的异步方法(get_p)直接返回Promise,不需要手动包装Subprocess;
  • 子进程里不能直接使用主进程的Mojolicious对象(比如$self),所有数据都要通过参数传递。

运行这两个版本的代码,你会发现总耗时大概在1秒左右,而不是串行的3秒,完美实现并行需求!

内容的提问来源于stack exchange,提问作者h q

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:59:32