【Swoole系列4.5】协程并发调度

2023-03-03 13:41:18 浏览数 (1)

协程并发调度

在学习了协程的通信功能 Channel 之后,我们紧接着就讲了一个 WaitGroup 功能。其实,它本身就是一个协程调度工具。关于它的作用我们不再赘述了,毕竟上一篇文章已经讲得很清楚明白了。今天我们再接着 WaitGroup 的话题,继续讲讲协程并发调度相关的内容。

协程执行与协程容器

学习到这里,不知道大家有没有发现一个问题,那就是如果不是在协程容器中,遇到阻塞的操作,协程就是顺序执行的。而如果在协程容器中,它就会变成并发执行的。

代码语言:javascript复制
go(function(){
   sleep(2);
   echo "cid1:" . Co::getCid() , PHP_EOL;
});
go(function(){
   sleep(1);
   echo "cid2:" . Co::getCid() , PHP_EOL;
});
//cid1:1
//cid2:2

上面的测试代码,第一个协程休息 2 秒,第二个休息 1 秒,最后输出的结果是顺序执行下来的。接下来我们把它放到协程容器中。

代码语言:javascript复制
SwooleCoroutinerun(function(){
  go(function(){
      sleep(2);
      echo "cid1:" . Co::getCid() , PHP_EOL;
  });
  go(function(){
      sleep(1);
      echo "cid2:" . Co::getCid() , PHP_EOL;
  });
});
//cid2:3
//cid1:2

在协程容器中,第二个协程先执行完并输出了内容,很明显,这是一种非常像并行运行的状态。那么实际上真的是这样吗?

其实这部分内容应该放到一键协程化中去讲解的,但是怕聪明的小伙伴们可能会提前发现这个问题,所以提前在这里简单地说一下。

其实协程并不是并行的,这个相信大家都已经清楚了,我们也说过很多次了,协程工作在线程之上,Swoole 是一个进程配一个线程的。协程没有并行能力,而是像函数一样执行的并发,但是,它是用户态的,我们可以手动操作挂起和恢复,也就是之前学习过的 yield() 和 resume() 的能力。因此,就可以通过这样的特性在 IO 等待的时候快速切换到其它协程进行处理,当这边的 IO 结束后再回来继续处理这个协程里面的内容。

协程容器实际上就是实现了一套内部的协程执行环境,让很多原本是同步执行的代码在容器中可以异步化。我们在容器外使用 co::sleep() 也可以实现挂起并发的操作,但在容器里面,直接 sleep() 就可以生效了。另外,Swoole 中,sleep() 不是什么好东西,我们为了演示可能会经常用到,但在真实的业务开发场景中,最好不要使用它。原因可以参考 Swoole 编程须知https://wiki.swoole.com/#/getting_started/notice?id=sleepusleep的影响 。

关于这一块的内容我们在协程篇最后的一键协程化中会再详细讲到。关于并行、并发的内容在最早的进程第一篇文章中也解释过了,不记得的小伙伴记得回去看看哦。

比 WaitGroup 更简单的调度

既然用到协程,那么我们肯定是需要它的并发能力,同时并发操作又有可能带来一些问题。其实就是我们上回讲过的一个业务多个协程并发执行完成的结果进行输出。当时我们使用的是 WaitGroup 来实现的等待多个协程完成执行同步返回的效果。但在 Swoole 中,还提供了一个更简单的工具 Barrier 。

代码语言:javascript复制
SwooleCoroutinerun(function () {
   $time = microtime(true);

   $barrier = SwooleCoroutineBarrier::make();

   foreach (range(1, 4) as $i) {
       go(function () use ($barrier, $i) {
           SwooleCoroutineSystem::sleep($i);
       });
   }

   SwooleCoroutineBarrier::wait($barrier);

   echo microtime(true) - $time, PHP_EOL;
});
// 4.0022649765015

其实它的性质和 WaitGroup 是一样的,但是省了一些步骤。从代码中可以看到,这个工具组件不需要我们手动的 add() 和 done() 了。使用它,我们只需要一开始 make() 一个对象,然后通过 use 将对象传递给协程就可以了。只要 use 中有这个 Barrier 对象,它就会开始自动计数。当协程执行完成后,它就会自动 done() 。最后,我们再使用 Barrier 的 wait() 方法进行等待监听即可。

是不是感觉比 WaitGroup 更方便了,如果协程很多的话,可以少写不少 add() 和 done() 方法哦。

异步服务器上的协程应用及调度

之前我们一直都是在命令行讲解协程,其实在服务器应用中也是一样的使用的,并且也是可以同样的进行协程的调度。

代码语言:javascript复制
$serv = new SwooleHttpServer("0.0.0.0", 9501, SWOOLE_PROCESS);

$serv->on('request', function ($req, $resp) {
    $time = microtime(true);
    $wg = new SwooleCoroutineWaitGroup();

    $wg->add();
    $wg->add();

    $res = 1;

    go(function () use ($wg, &$res) {
        co::sleep(3);
        $res  = 1;
        $wg->done();
    });

    go(function () use ($wg, &$res) {
        co::sleep(4);
        $res *= 10;
        $wg->done();
    });

    $wg->wait();

    $endTime = microtime(true) - $time;
    $resp->end($res . " - " . $endTime);
});
$serv->start();

//20 - 4.002151966095

上面这段我们就是通过 WaitGroup 进行的同步等待,大家可以试着自己换成 Barrier 看看效果哦。

总结

今天的内容比较简单,毕竟有了之前的基础之后,我们学习理解新的概念的难度会越来越低。最主要的是,一定要想明白想清楚异步执行同步返回的概念,理解为什么要这样做。这一块更多的资料可以搜索之前提过的 JS 中的 Promise 相关的内容。

测试代码:

https://github.com/zhangyue0503/swoole/blob/main/4.Swoole协程/source/4.5协程并发调度.php

参考文档:

https://wiki.swoole.com/#/coroutine/barrier

https://wiki.swoole.com/#/coroutine/multi_call?

0 人点赞