1. 云栖社区>
  2. PHP教程>
  3. 正文

54-PHP_Yield协程从入门到精通

作者:用户 来源:互联网 时间:2017-12-01 11:33:35

phpyield入门精通程从

54-PHP_Yield协程从入门到精通 - 摘要: 本文讲的是54-PHP_Yield协程从入门到精通,PHP Yield协程从入门到精通协程基本概念“协程”(Coroutine)概念最早由 Melvin Conway 于1958年提出。协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用

PHP Yield协程从入门到精通
协程
基本概念

“协程”(Coroutine)概念最早由 Melvin Conway 于1958年提出。协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。总的来说,协程为协同任务提供了一种运行时抽象,这种抽象非常适合于协同多任务调度和数据流处理。在现代操作系统和编程语言中,因为用户态线程切换代价比内核态线程小,协程成为了一种轻量级的多任务模型。


从编程角度上看,协程的思想本质上就是控制流的主动让出(yield)和恢复(resume)机制,迭代器常被用来实现协程,所以大部分的语言实现的协程中都有yield关键字,比如Python、PHP、Lua。但也有特殊比如Go就使用的是通道来通信。


协程与进程线程的区别
对于操作系统来说只有进程和线程,协程的控制由应用程序显式调度,非抢占式的
协程的执行最终靠的还是线程,应用程序来调度协程选择合适的线程来获取执行权
切换非常快,成本低。一般占用栈大小远小于线程(协程KB级别,线程MB级别),所以可以开更多的协程
协程比线程更轻量级
PHP与协程

PHP从5.5引入了yield关键字,增加了迭代生成器和协程的支持,但并未在语言本身级别实现一个完善的协程解决方案。PHP协程也是基于Generator,Generator可以视为一种“可中断”的函数,而 yield 构成了一系列的“中断点”。PHP 协程没有resume关键字,而是“在使用的时候唤起”协程。了解如何在PHP中实现协程,首先要解决迭代生成器。


function xrange($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i += $step) {
yield $i;
}
}
 
foreach (xrange(1, 1000000) as $num) { // xrange返回的是一个Generator对象
echo $num, "/n";
}

具体参考



PHP > 手册 > 语言参考 > 生成器


中断点

我们从生成器认识协程,需要认识到:生成器是一种具有中断点的函数,而yield构成了中断点。比如, 你调用$range->rewind(),那么xrange()里的代码就会运行到控制流第一次出现yield的地方,而函数内传递给yield语句的值,即为迭代的当前值,可以通过$xrange->current()获取。


PHP中的协程实现

PHP的协程支持是在迭代生成器的基础上,增加了可以回送数据给生成器的功能,从而达到双向通信即:


生成器<---数据--->调用者


yield接收与发送数据
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())
function gen() {
yield 'foo';
yield 'bar';
}
 
$gen = gen();
var_dump($gen->send('something'));
 
// 在send之前当$gen迭代器被创建的时候一个renwind()方法已经被隐式调用
// 所以实际上发生的应该类似:
//$gen->rewind();
//var_dump($gen->send('something'));
 
//这样renwind的执行将会导致第一个yield被执行, 并且忽略了他的返回值.
//真正当我们调用yield的时候, 我们得到的是第二个yield的值! 导致第一个yield的值被忽略.
//string(3) "bar"
协程与任务调度

yield指令提供了任务中断自身的一种方法,然后把控制交回给任务调度器。而PHP语言本身只是提供程序中断的功能,至于任务调度器需要我们自己实现,同时协程在运行多个其他任务时,yield还可以用来在任务和调度器之间进行通信。


PHP协程任务

简单的定义具有任何ID标识的协程函数,如一个轻量级的协程函数示例代码:


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

newTask()方法创建一个新任务,然后把这个任务放入任务map数组里,接着它通过把任务放入任务队列里来实现对任务的调度。接着run()方法扫描任务队列,运行任务,如果一个任务结束了,那么它将从队列里删除,否则它将在队列的末尾再次被调度。


协程示例:


function task1() {
for ($i = 1; $i <= 10; ++$i) {
echo "This is task 1 iteration $i./n";
yield;
}
}
 
function task2() {
for ($i = 1; $i <= 5; ++$i) {
echo "This is task 2 iteration $i./n";
yield;
}
}
 
$scheduler = new Scheduler;
 
$scheduler->newTask(task1());
$scheduler->newTask(task2());
 
$scheduler->run();

结果如下


This is task 1 iteration 1.
This is task 2 iteration 1.
This is task 1 iteration 2.
This is task 2 iteration 2.
This is task 1 iteration 3.
This is task 2 iteration 3.
This is task 1 iteration 4.
This is task 2 iteration 4.
This is task 1 iteration 5.
This is task 2 iteration 5.
This is task 1 iteration 6.
This is task 1 iteration 7.
This is task 1 iteration 8.
This is task 1 iteration 9.
This is task 1 iteration 10.

以上是云栖社区小编为您精心准备的的内容,在云栖社区的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索php , yield , 入门 , , 精通 程从 ,以便于您获取更多的相关知识。