php 中的隐藏备注,php 协程 yield 一些备注

协程的概念

协程( Coroutine)又名纤程,是一种用户态的轻量级线程。协程不受内核调度,协程的切换完全由程序自己掌控,操作系统对协程无感知。协程拥有自己的寄存器上下文和栈。协程调度切换时(通常是协程主动让出CPU执行权),将寄存器上下文和栈保存,在切换回来时,再恢复先前保存的寄存器上下文和栈。

php中基于yield关键字的协程

php从5.5版起增加了yield关键字。使用了yield关键字的函数又被成为生成器函数。与return关键字不同的是,yield关键字实际上返回的是一个生成器对象(Generator class)而非一个值,zend引擎会为这个生成器对象开辟一块独立的堆栈空间,从而使得每一个生成器对象可以保存自己的状态。

生成器对象每次在收到迭代的指令后,会从之前的中断处即(yield关键字标识的地方)重新执行,直到再次遇到yield关键字,这是便保存自己当前的状态并暂时中断。

下面的代码中,在loop函数里使用了yield关键字,每次调用这个生成器函数时,都会从之前的中断处执行。

function loop($start, $end, $step = 1) {

for ($i = $start; $i <= $end; $i += $step) {

yield $i;

}

}

$loop = loop(1, 10);

var_dump($loop); // 返回的是一个生成器对象 object(Generator)

var_dump($loop->current()); //获取生成器对象当前的值,输出1

$loop->next();//使生成器对象运行到下一个yield处

var_dump($loop->current()); //输出2

foreach (loop(1, 5) as $num) { //也可以使用foreach进行迭代

echo $num, "\n";

}

我们知道,cpu(单核)实际上在一个时刻只能执行一个任务,但为了能让计算机用户觉得任务好像是在同时运行的(比如一边编辑文档一边通过浏览器看新闻),cpu需要在多个任务(进程)之间切换。而cpu处理哪一个任务则由操作系统决定,操作系统可以剥夺进程的执行权,将cpu执行权分配给其他进程。

而基于协程实现的”并发执行”(不是真的并发执行),则是多任务协作式的。当前正在运行的某个任务完成了它目前所能做的工作后,自动让出cpu资源,将控制权交还给调度器。

这两种方式(多任务抢占式和多任务协作式)不变的是,它能使进程具有“对称切换能力 ”,也就是进程a可以切换到进程b,进程b也可以再切换到进程a(区别于传统的父子函数式的调用)。

yield之所以能在php层面实现协程,就是因为yield关键字让函数可以具有多个“返回点”,使函数可以对称式地切换。

下面是一个简单的多任务协作的例子:

首先定义任务类

每个任务有自己的编号,和一个是生成器对象的成员变量,每次调用任务的run方法时就对生成器对象进行迭代。

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();

}

}

然后是调度器类

调度器通过newTask方法创建新任务并将其入队,当调用run方法运行时,遍历任务队列并逐个执行,然后检测任务(协程)是否执行结束,如果任务(协程)还需要再次执行,那么将其重新入队,等待下一次执行。

class Scheduler {

protected $maxTaskId = 0;

protected $taskMap = []; // taskId => task

protected $taskQueue;

public function __construct() {

$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();

if ($task->isFinished()) {

unset($this->taskMap[$task->getTaskId()]);

} else {

$this->schedule($task);

}

}

}

}

然后是task1和task2(虽然意义不大)

运行后可以看到两个任务是交替执行的。

function task1() {

for ($i = 1; $i <= 10; ++$i) {

echo "task 1 iteration $i.\n";

yield;

}

}

function task2() {

for ($i = 1; $i <= 5; ++$i) {

echo "task 2 iteration $i.\n";

yield;

}

}

$scheduler = new Scheduler;

$scheduler->newTask(task1());

$scheduler->newTask(task2());

$scheduler->run();

/*output:

task 1 iteration 1.

task 2 iteration 1.

task 1 iteration 2.

task 2 iteration 2.

task 1 iteration 3.

task 2 iteration 3.

task 2 iteration 4.

task 2 iteration 5.

*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值