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写法直接变成同步的写法;

原创粉丝点击