如何在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
相关产品推荐
相关产品推荐

