之前在python里见过这个yield关键字,一致没有意识到php也有这个关键字,查了一些文章,说yield跟php协程有关系,到底什么是yield,又跟协程有什么关系?今天来盘盘
一、yield
在方法中使用,类似return,但是它不会终止方法的执行;会生成一个生成器,可以遍历生成器;只有遍历生成器的时候才会执行方法里面的代码
1、foreach遍历
用到的方法:
rewind: // 跳转到第一个yield之前,可以检测yield之前的代码是否有问题;如果遍历已经开始,执行该方法会抛出异常!
getReturn: 获取方法的返回值
function say() {
echo 'start';
for ($i=0;$i<3;$i++) {
yield 'i='.$i;
}
return 'finish';
}
$gen = say();
$gen->rewind();
echo 'foreach'.PHP_EOL;
foreach ($gen as $key => $val) {
echo 'key='.$key.' val='.$val.PHP_EOL;
}
echo $gen->getReturn();
结果:
2、对象遍历
新用到了几个操作:
instanceof :判断是不是生成器
key:获取当前生成器索引
current:获取当前生成器的值
next:生成器继续执行
send:1.向生成器发送一个数据替代yield的返回值,2.生成器继续执行,这点类似next
<?php
function say() {
echo 'start'.PHP_EOL;
for ($i=0;$i<3;$i++) {
echo 'inner i='.$i.PHP_EOL;
$name = yield 'i='.$i;
echo 'send-in:'.$name.PHP_EOL;
}
return 'finish';
}
$gen = say();
if ($gen instanceof Generator) {
echo 'true'.PHP_EOL;
} else {
echo 'false'.PHP_EOL;
}
$gen->rewind();
echo 'start-iterate'.PHP_EOL;
echo 'key='.$gen->key().' val='.$gen->current().PHP_EOL;
$gen->next();
echo 'key='.$gen->key().' val='.$gen->current().PHP_EOL;
echo $gen->send('哈哈').PHP_EOL;
echo '------------'.PHP_EOL;
echo 'key='.$gen->key().' val='.$gen->current().PHP_EOL;
echo '------------'.PHP_EOL;
$gen->next();
echo 'key='.$gen->key().' val='.$gen->current().PHP_EOL;
echo $gen->getReturn();
// 结果:
到这里yield基本了解差不都了,开始研究yield和协程的关系,我们知道协程是用户态,这点跟swoole的协程一样;php的协程是进程级,比go的多进程级协程安全;协程在用户态操作,创建,销毁的开销都很小;
二、php yield实现协程
1、为了好理解,多任务没有定义为类
<?php
// 多任务方法
function t($n)
{
for($i=0;$i<$n;$i++){
echo '总次数:'.$n.' 当前次数:'.$i.PHP_EOL;
yield; // 一个生成器
}
return $n;
}
// 放入PHP的SplQueue
$t1 = t(3);
$t2 = t(5);
// 任务调度
class schedule
{ public $taskQueue;
public function __construct()
{
$this->taskQueue = new SplQueue();
}
public function add($task)
{
$this->taskQueue->enqueue($task);
}
public function run()
{
while(!$this->taskQueue->isEmpty()){
$task = $this->taskQueue->dequeue();
$task->next();
if ($task->valid()) {
$this->taskQueue->enqueue($task);
} else {
echo 'return'.$task->getReturn().PHP_EOL;
}
}
}
}
$s = new schedule();
$s->add($t1);
$s->add($t2);
$s->run();
// 结果
2、升级版task使用一个类
<?php
class Task
{
private $isFirst = true;
private $sendData = null;
private $coroutine = null;
public function __construct(Generator $gen)
{
$this->coroutine = $gen;
}
public function isFinished()
{
return !$this->coroutine->valid();
}
public function run()
{
if ($this->isFirst){
$this->isFirst = false;
} else {
$this->coroutine->send($this->sendData); // 继续迭代器
}
}
}
Class Scheduler
{
/**
* @var SplQueue
*/
protected $taskQueue;
/**
* @var int
*/
protected $tid = 0;
/**
* Scheduler constructor.
*/
public function __construct()
{
/* 原理就是维护了一个队列,
* 前面说过,从编程角度上看,协程的思想本质上就是控制流的主动让出(yield)和恢复(resume)机制
* */
$this->taskQueue = new SplQueue();
}
/**
* 增加一个任务
*
* @param Generator $task
* @return int
*/
public function addTask(Generator $task)
{
$tid = $this->tid;
$task = new Task($task);
$this->taskQueue->enqueue($task);
$this->tid++;
return $tid;
}
/**
* 把任务进入队列
*
* @param Task $task
*/
public function schedule(Task $task)
{
$this->taskQueue->enqueue($task);
}
/**
* 运行调度器
*/
public function run()
{
while (!$this->taskQueue->isEmpty()) {
// 任务出队
$task = $this->taskQueue->dequeue();
$res = $task->run(); // 运行任务直到 yield
if (!$task->isFinished()) {
$this->schedule($task); // 任务如果还没完全执行完毕,入队等下次执行
}
}
}
}
function task1() {
for ($i = 1; $i <= 3; ++$i) {
echo "task1 iteration $i\n";
yield;
}
}
function task2() {
for ($i = 1; $i <= 5; ++$i) {
echo "task2 iteration $i\n";
yield;
}
}
$scheduler = new Scheduler; // 实例化一个调度器
$scheduler->addTask(task1()); // 添加不同的闭包函数作为任务
$scheduler->addTask(task2()); // 添加不同的闭包函数作为任务
$scheduler->run();
// 结果
3、协程堆栈(yield里面嵌套yield)
<?php
class Task
{
private $isFirst = true;
private $sendData = null;
private $coroutine = null;
public function __construct(Generator $gen)
{
$this->coroutine = new HandleCoroutine($gen);
}
public function isFinished()
{
return !$this->coroutine->gen->valid();
}
public function run()
{
if ($this->isFirst){
$this->isFirst = false;
$this->coroutine->run();
} else {
$this->coroutine->gen->next(); // 继续迭代器
$this->coroutine->run();// 执行迭代器
}
}
}
Class Scheduler
{
/**
* @var SplQueue
*/
protected $taskQueue;
/**
* @var int
*/
protected $tid = 0;
/**
* Scheduler constructor.
*/
public function __construct()
{
/* 原理就是维护了一个队列,
* 前面说过,从编程角度上看,协程的思想本质上就是控制流的主动让出(yield)和恢复(resume)机制
* */
$this->taskQueue = new SplQueue();
}
/**
* 增加一个任务
*
* @param Generator $task
* @return int
*/
public function addTask(Generator $task)
{
$tid = $this->tid;
$task = new Task($task);
$this->taskQueue->enqueue($task);
$this->tid++;
return $tid;
}
/**
* 把任务进入队列
*
* @param Task $task
*/
public function schedule(Task $task)
{
$this->taskQueue->enqueue($task);
}
/**
* 运行调度器
*/
public function run()
{
while (!$this->taskQueue->isEmpty()) {
// 任务出队
$task = $this->taskQueue->dequeue();
$res = $task->run(); // 运行任务直到 yield
if (!$task->isFinished()) {
$this->schedule($task); // 任务如果还没完全执行完毕,入队等下次执行
}
}
}
}
class HandleCoroutine
{
public $gen;
public $stock;
public function __construct(Generator $gen)
{
$this->gen = $gen;
$this->stock = new SplStack;
}
public function run()
{
// 遍历子生成器
$val = $this->gen->current();
if ($val instanceof Generator) {
foreach ($val as $key => $v) {
// 遍历子节点
}
}
}
}
function say($max) {
for ($i = 1; $i <= $max; ++$i) {
echo "task{$max} iteration $i\n";
yield;
}
}
function task1() {
yield say(3);
yield say(4);
}
function task2() {
yield say(5);
}
$scheduler = new Scheduler; // 实例化一个调度器
$scheduler->addTask(task1()); // 添加任务
// $scheduler->addTask(task2());
$scheduler->run();
-
在HandleCoroutine对子协程进行循环遍历
- end ~