首页 > 代码库 > PHP_Yield协程从入门到精通
PHP_Yield协程从入门到精通
本文和大家分享的主要是PHP中Yield协程相关内容,一起来看看吧,希望对大家学习php有所帮助。
协程
基本概念
“协程”(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标识的协程函数,如一个轻量级的协程函数示例代码:
<?phpclass Task {
protected $taskId;
protected $coroutine;
protected $sendValue = http://www.mamicode.com/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 = http://www.mamicode.com/$sendValue;
}
public function run() {
if ($this->beforeFirstYield) {
$this->beforeFirstYield = false;
return $this->coroutine->current();
} else {
$retval = $this->coroutine->send($this->sendValue);
$this->sendValue = http://www.mamicode.com/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.
来源:PHPBoy
协程
基本概念
“协程”(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标识的协程函数,如一个轻量级的协程函数示例代码:
<?phpclass Task {
protected $taskId;
protected $coroutine;
protected $sendValue = http://www.mamicode.com/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 = http://www.mamicode.com/$sendValue;
}
public function run() {
if ($this->beforeFirstYield) {
$this->beforeFirstYield = false;
return $this->coroutine->current();
} else {
$retval = $this->coroutine->send($this->sendValue);
$this->sendValue = http://www.mamicode.com/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.
来源:PHPBoy
PHP_Yield协程从入门到精通
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。