yield生成器

PHP迭代器学习

yield关键字

$arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
$arr2 = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];

function demo ($arr) {
    foreach ($arr as $value) {
        yield $value;
    }
}

$obj1 = demo($arr1);
$obj2 = demo($arr2);

while (true) {
    // $obj1->key(), $obj2->key() 为零
    $value1 = $obj1->current();
    $value2 = $obj2->current();
    $value = $value1 * $value2;
    echo $value.'<br>';
    // $obj1->key(), $obj2->key() 加1
    $obj1->next();
    $obj2->next();
    // 是否完成迭代(obj->next()后程序找不到yield,既为失效)
    if (!$obj1->valid() || !$obj2->valid()) {
        break;
    }
}

输出结果:10 40 90 160 250 360 490 640 810 1000

生成器的方法

  • current() 返回当前产生的值,即是yield返回给生成器的值
  • key() 返回当前产生的键,当yield返回的不是键值对的时候,默认使用索引键
  • next() 生成器继续执行,通过调用生成器的next()来继续执行被yield阻塞的后面的代码,没有返回值
  • rewind() 重置迭代器,如果迭代已经开始,调用该方法会抛出一个异常,每次调用生成器函数生成生成器的时候会自动调用(生成迭代对象的时候已经隐含地执行了rewind操作)一次rewind(),没有返回值
  • valid() 检查迭代器是否被关闭,即迭代是否已经结束,返回true/false
  • send() 向生成器中传入一个值,并将这个值传递给当前yield,替换整个yield表达式,然后继续执行生成器(无须显式调用next()触发程序继续执行),返回下一个yield的返回值。

说明

如果你调用->rewind(), 代码就会运行到控制流第一次出现yield的地方. 而函数内传递给yield语句的返回值可以通过$range->current()获取.
为了继续执行生成器中yield后的代码, 你就需要调用next()方法. 这将再次启动生成器, 直到下一次yield语句出现. 因此,连续调用next()和current()方法, 你就能从生成器里获得所有的值, 直到再没有yield语句出现.

yield替换

  • yield两边括号在send时参数替换
  • yield表达式两边的括号在PHP7以前不是可选的, 也就是说在PHP5.5和PHP5.6中圆括号是必须的.
function gen() {
    $ret = (yield 'yield1');
    var_dump($ret);
    $ret = (yield 'yield2');
    var_dump($ret);
}

$gen = gen();
var_dump($gen->current());    // string(6) "yield1"
var_dump($gen->send('ret1')); // string(4) "ret1"   (the first var_dump in gen)
                              // string(6) "yield2" (the var_dump of the ->send() return value)
var_dump($gen->send('ret2')); // string(4) "ret2"   (again from within gen)
                              // NULL               (the return value of ->send())

任务轻量级协程函数

<?php
class Task {
    protected $taskId;
    protected $coroutine;
    protected $sendValue = null;
    protected $beforeFirstYield = true;
    public function __construct($taskId, Generator $coroutine) {
        $this->taskId = $taskId;
        $this->coroutine = $coroutine;
    }
    public function getTaskId() {
        return $this->taskId;
    }
    public function setSendValue($sendValue) {
        $this->sendValue = $sendValue;
    }
    public function run() {
        if ($this->beforeFirstYield) {
            $this->beforeFirstYield = false;
            return $this->coroutine->current();
        } else {
            $retval = $this->coroutine->send($this->sendValue);
            $this->sendValue = null;
            return $retval;
        }
    }
    public function isFinished() {
        return !$this->coroutine->valid();
    }
}
<?php
class Scheduler {
    protected $maxTaskId = 0;
    protected $taskMap = []; // taskId => task
    protected $taskQueue;
    public function __construct() {
        // php 内置队列
        $this->taskQueue = new SplQueue();
    }
    public function newTask(Generator $coroutine) {
        $tid = ++$this->maxTaskId;
        $task = new Task($tid, $coroutine);
        $this->taskMap[$tid] = $task;
        $this->schedule($task);
        return $tid;
    }
    public function schedule(Task $task) {
        // 加入队列
        $this->taskQueue->enqueue($task);
    }
    public function run() {
        while (!$this->taskQueue->isEmpty()) {
            // 取出队列
            $task = $this->taskQueue->dequeue();
            $task->run();
            // 任务是否valid ?
            if ($task->isFinished()) {
                unset($this->taskMap[$task->getTaskId()]);
            } else {
                // 加入队列继续
                $this->schedule($task);
            }
        }
    }
}

调用:

$scheduler = new Scheduler;
$scheduler->newTask(task1());
$scheduler->newTask(task2());
$scheduler->run();