PHP7下协程的实现方法详解

时间:2018-01-15

所以你在一个协程函数中嵌套另外一个协程函数:

<?php
function echoTimes($msg, $max) {
 for ($i = 1; $i <= $max; ++$i) {
  echo "$msg iteration $i\n";
  yield;
 }
}
function task() {
 echoTimes('foo', 10); // print foo ten times
 echo "---\n";
 echoTimes('bar', 5); // print bar five times
 yield; // force it to be a coroutine
}
$scheduler = new Scheduler;
$scheduler->newTask(task());
$scheduler->run();

这里的echoTimes是执行不了的!所以就需要协程堆栈。

不过没关系,我们改一改我们刚刚的代码。

把Task中的初始化方法改下,因为我们在运行一个Task的时候,我们要分析出他包含了哪些子协程,然后将子协程用一个堆栈保存。(C语言学的好的同学自然能理解这里,不理解的同学我建议去了解下进程的内存模型是怎么处理函数调用)

 /**
  * Task constructor.
  * @param $taskId
  * @param Generator $coroutine
  */
 public function __construct($taskId, Generator $coroutine)
 {
  $this->taskId = $taskId;
  // $this->coroutine = $coroutine;
  // 换成这个,实际Task->run的就是stackedCoroutine这个函数,不是$coroutine保存的闭包函数了
  $this->coroutine = stackedCoroutine($coroutine); 
 }

当Task->run()的时候,一个循环来分析:

/**
 * @param Generator $gen
 */
function stackedCoroutine(Generator $gen)
{
 $stack = new SplStack;
 // 不断遍历这个传进来的生成器
 for (; ;) {
  // $gen可以理解为指向当前运行的协程闭包函数(生成器)
  $value = $gen->current(); // 获取中断点,也就是yield出来的值
  if ($value instanceof Generator) {
   // 如果是也是一个生成器,这就是子协程了,把当前运行的协程入栈保存
   $stack->push($gen);
   $gen = $value; // 把子协程函数给gen,继续执行,注意接下来就是执行子协程的流程了
   continue;
  }
  // 我们对子协程返回的结果做了封装,下面讲
  $isReturnValue = $value instanceof CoroutineReturnValue; // 子协程返回`$value`需要主协程帮忙处理 
  if (!$gen->valid() || $isReturnValue) {
   if ($stack->isEmpty()) {
    return;
   }
   // 如果是gen已经执行完毕,或者遇到子协程需要返回值给主协程去处理
   $gen = $stack->pop(); //出栈,得到之前入栈保存的主协程
   $gen->send($isReturnValue ? $value->getValue() : NULL); // 调用主协程处理子协程的输出值
   continue;
  }
  $gen->send(yield $gen->key() => $value); // 继续执行子协程
 }
}

然后我们增加echoTime的结束标示:

class CoroutineReturnValue {
 protected $value;
 
 public function __construct($value) {
  $this->value = $value;
 }
 // 获取能把子协程的输出值给主协程,作为主协程的send参数
 public function getValue() {
  return $this->value;
 }
}
function retval($value) {
 return new CoroutineReturnValue($value);
}

然后修改echoTimes:

function echoTimes($msg, $max) {
 for ($i = 1; $i <= $max; ++$i) {
  echo "$msg iteration $i\n";
  yield;
 }
 yield retval(""); // 增加这个作为结束标示
}

Task变为:

function task1()
{
 yield echoTimes('bar', 5);
}

这样就实现了一个协程堆栈,现在你可以举一反三了。

4)PHP7中yield from关键字

PHP7中增加了yield from,所以我们不需要自己实现携程堆栈,真实太好了。

把Task的构造函数改回去:

 public function __construct($taskId, Generator $coroutine)
 {
  $this->taskId = $taskId;
  $this->coroutine = $coroutine;
  // $this->coroutine = stackedCoroutine($coroutine); //不需要自己实现了,改回之前的
 }

echoTimes函数:

function echoTimes($msg, $max) {
 for ($i = 1; $i <= $max; ++$i) {
  echo "$msg iteration $i\n";
  yield;
 }
}

task1生成器:

function task1()
{
 yield from echoTimes('bar', 5);
}

这样,轻松调用子协程。

总结

这下应该明白怎么实现PHP协程了吧?

  • 共4页:
  • 上一页
  • 3/4
  • 下一页
  • 上一篇:PHP结合Vue实现滚动底部加载效果 下一篇:浅谈PHP实现大流量下抢购方案

    相关文章

    最新文章