PHP协程(1):简略
来源:互联网 发布:mac上翻译软件 编辑:程序博客网 时间:2024/06/03 14:08
基本概念
援引几个博客上的话:
对于单核处理器,多进程实现多任务的原理是让操作系统给一个任务每次分配一定的 CPU时间片,然后中断、让下一个任务执行一定的时间片接着再中断并继续执行下一个,如此反复。由于切换执行任务的速度非常快,给外部用户的感受就是多个任务的执行是同时进行的。多进程的调度是由操作系统来实现的,进程自身不能控制自己何时被调度,也就是说:
进程的调度是由外层调度器抢占式实现的
而协程要求当前正在运行的任务自动把控制权回传给调度器,这样就可以继续运行其他任务。这与『抢占式』的多任务正好相反,抢占多任务的调度器可以强制中断正在运行的任务, 不管它自己有没有意愿。『协作式多任务』在 Windows 的早期版本 (windows95)和 Mac OS 中有使用,不过它们后来都切换到『抢占式多任务』了。理由相当明确:如果仅依靠程序自动交出控制的话,那么一些恶意程序将会很容易占用全部 CPU时间而不与其他任务共享。
协程的调度是由协程自身主动让出控制权到外层调度器实现的
协程可以理解为纯用户态的线程,通过协作而不是抢占来进行任务切换。相对于进程或者线程,协程所有的操作都可以在用户态而非操作系统内核态完成,创建和切换的消耗非常低。简单的说 Coroutine(协程) 就是提供一种方法来中断当前任务的执行,保存当前的局部变量,下次再过来又可以恢复当前局部变量继续执行。
转载来源:http://www.jianshu.com/p/edef1cb7fee6
然后看下韩天峰对于应用协程的看法:
当程序员还沉浸在解决C10K问题带来的成就感时,一个新的问题被抛出了。异步嵌套回调太TM难写了。尤其是Node.js层层回调,缩进了几十层,要把程序员逼疯了。于是一个新的技术被提出来了,那就是协程(coroutine)。这个技术本质上也是异步非阻塞技术,它是将事件回调进行了包装,让程序员看不到里面的事件循环。程序员就像写阻塞代码一样简单。比如调用 client->recv() 等待接收数据时,就像阻塞代码一样写。实际上是底层库在执行recv时悄悄保存了一个状态,比如代码行数,局部变量的值。然后就跳回到EventLoop中了。什么时候真的数据到来时,它再把刚才保存的代码行数,局部变量值取出来,又开始继续执行。
这个就像时间禁止的游戏一样,国王对巫师说“我必须马上得到宝物,不然就砍了你的脑袋”,巫师念了一句时间停止的咒语,直到过了1年后勇士们才把宝物送来。这时候巫师解开咒语,把宝物交给国王。这里国王就可以理解成协程,他根本没感觉到时间停止,在他停止到醒来期间发生了什么他不知道,也不关心。
这就是协程的本质。协程是异步非阻塞的另外一种展现形式。Golang,Erlang,Lua协程都是这个模型。
转载地址:http://rango.swoole.com/archives/381
这里单单看这一段可能无法理解,建议去看下这篇转载地址的全文;这里其实说的就是协程对于异步的应用场景;
PHP的协程
PHP实现协程,简单点说是通过yield关键字,准确点说是通过生成器generator类来实现的;
看PHP里面通过generator实现的释放控制:
例子1
function task(){ echo "生成器 内 第一代码段" . PHP_EOL; yield; echo "生成器 内 第二代码段" . PHP_EOL; yield; echo "生成器 内 第三代码段" . PHP_EOL;}$task = task(); // 初始化生成器$task->current(); // 执行到第一个yield$task->next(); // 执行到第二个yield$task->next(); // 执行第二个yield之后的代码段/** * 输出: * 生成器 内 第一代码段 * 生成器 内 第二代码段 * 生成器 内 第三代码段 */
这个非常就简单的例子里面,task函数里面的代码的执行是可控的,什么时候中断,什么时候执行,是由生成器控制的;而这个中断,就是释放控制权,代码可以继续执行下去;
Generator生成器可以参考http://blog.csdn.net/alexander_phper/article/details/78523876;
再看一个例子:
例子2
function task(){ // 这里执行的代码称为生成器内 echo "生成器 内 第一代码段" . PHP_EOL; yield; echo "生成器 内 第二代码段" . PHP_EOL; yield; echo "生成器 内 第三代码段" . PHP_EOL;}// 这里执行的代码称为生成器外$task = task(); // 初始化生成器echo "生成器 外 第一代码段" . PHP_EOL;$task->current();echo "生成器 外 第二代码段" . PHP_EOL;$task->next();echo "生成器 外 第三代码段" . PHP_EOL;$task->next();echo "生成器 外 第四代码段" . PHP_EOL;/** * 输出: * 生成器 外 第一代码段 * 生成器 内 第一代码段 * 生成器 外 第二代码段 * 生成器 内 第二代码段 * 生成器 外 第三代码段 * 生成器 内 第三代码段 * 生成器 外 第四代码段 */
生成器内外的代码在交替执行着;当运行到生成器里面的yield关键字的时候,程序会释放控制权,执行别的程序;
PHP协程实现多任务交替执行
生成器可以中断执行,将控制权交出去,那我们可以让程序在多个生成器中间切换;
例子3
function task1(){ echo "生成器1 内 第一代码段" . PHP_EOL; yield; echo "生成器1 内 第二代码段" . PHP_EOL; yield; echo "生成器1 内 第三代码段" . PHP_EOL;}function task2(){ echo "生成器2 内 第一代码段" . PHP_EOL; yield; echo "生成器2 内 第二代码段" . PHP_EOL; yield; echo "生成器2 内 第三代码段" . PHP_EOL;}$task1 = task1(); // 初始化生成器$task2 = task2();$task1->current();$task2->current();$task1->next();$task2->next();$task1->next();$task2->next();/** * 输出: * 生成器1 内 第一代码段 * 生成器2 内 第一代码段 * 生成器1 内 第二代码段 * 生成器2 内 第二代码段 * 生成器1 内 第三代码段 * 生成器2 内 第三代码段 */
现在看起来我们可以在两个函数中切换着执行(其实是两个生成器中切换着执行);
这种切换任务的实现方法非常低级,需要手动执行生成器;我们看那一篇原文中的实现方式;
(转载地址:http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html)
(翻译版地址:http://www.laruence.com/2015/05/28/3038.html);
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); } } }}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(); }}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. */
这里面引入的调度器类Scheduler和任务类Task(一个任务类可以简单理解就是一个生成器generator),调度器依次控制所有任务执行一次,任务执行一次就是运行到generator的一个yield;所以调度器可以依次执行task1和task2中的代码;当task2中的代码运行完毕之后,继续运行task1中的代码,直到task1中的代码也运行完毕;
现在应该有一个疑问,协程可以干嘛呢?
上面协程那一篇原文中有socket非阻塞IO的应用,可查看鸟哥的翻译版;
不过协程配合异步是个非常好的应用场景;可以查看韩天峰上面的说法;本人也是经过swoole的异步才接触到协程这个概念,现在已经用了很久,但是对协程却很模糊,这才有了这一篇文章;下一篇让我们看下通过携程堆栈实现异步回调,把异步的callback写法直接变成同步的写法;
- PHP协程(1):简略
- 编译OpenCV3.1简略教程
- Cobub Razor 开源PHP 移动统计CI系统 简略分析
- Bézier Curve 简略论述(1)
- AJAX开发简略续一(1)
- c++primer 1-3章简略笔记
- [李景山php] 深入理解PHP内核[读书笔记]--第三章:变量及数据类型--简略
- 《程序员修炼之道》简略笔记:1-4章
- 《Effecitve C++》简略笔记——1~17
- 百度地图3.1.1开发简略之地图闪烁图标
- ibatis简略
- node简略
- static简略
- XML简略
- C++反汇编揭秘1 一个简略地C++程序反汇编解释分析
- 【cocos2d-x-3.1.1系列4】cocos2d-x3.1.1.渲染过程源码简略过程
- 图像融合之泊松编辑(Poisson Editing)(1):简略语言概述算法
- 图像融合之泊松编辑(Poisson Editing)(1):简略语言概述算法
- iOS-NSJSONReadingMutableContainers,NSJSONReadingMutableLeaves,NSJSONReadingAllowFragments,NSJSONWrit
- 记录一次某医院影像无法正常传输的处理过程
- python里使用正则表达式来分割字符串
- /bin/bash^M: bad interpreter: 没有那个文件或目录
- Spring IoC — 基于Java类的配置
- PHP协程(1):简略
- dismiss到跟视图
- (转)Android启动界面先白屏或者黑屏然后才出现画面的解决办法
- 使用node.js与socket.io搭建即时聊天系统
- Eclipse Java注释模板设置详解
- Celery-4.1 用户指南: Signals
- 面试 jdk代理与cglib代理的区别
- SQL SERVER GETDATE()函数用法
- idea2017的hadoop的wordcount打成jar放到linux执行步骤