php yield学习

本文深入探讨了PHP中的`yield`关键字及其与协程的关系。`yield`用于生成器函数,允许在不终止函数执行的情况下返回值。通过遍历生成器,可以逐次执行函数中的代码。在PHP中,协程是用户态的,相比其他语言的协程更安全。文章通过示例展示了如何使用`yield`创建和调度协程任务,包括简单的多任务管理和协程堆栈的实现。
摘要由CSDN通过智能技术生成

之前在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 ~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值