第一部分 什么是协程(Coroutine)
一、概念
0、先来看看Wikipedia Coroutine上的定义(Wikipedia协程中文定义):
Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed。
1、再来看看Wikipedia Routine定义中Coroutine的描述
Coroutine, generalized reentrant computer subroutine having multiple entry points(协程,具有多个入口点的广义可重入计算机子例程
)
2、我的理解(纯属个人理解)
先说明白routine和subrtoutine
routine: computer program, 就是程序,就是一个进程或一个线程。
subroutine: a routine inside another routine, 就是一个子程序,程序的一部分,换句话说就是一个子任务。
注:在Linux操作系统看来进程和线程都是一个task,这里为了方便阐述,以下用线程来表示线程和进程。
协程(coroutine)是一个子程序,是一类特殊的子程序。特殊子程序和普通的子程序的区别在于,普通的多个子程序构成了一个线程,这些普通子程序是不可分割的,是一个整体,不能被调用者(用户态)调用。特殊的多个子程序构成了一个线程,这些多个特殊的子程序是可以分割的,做为多个独立的子任务,被调用者(用户态的)分开调用。当然,不管是普通子程序构成的线程,还是特殊子程序构成的线程,都是被Linux操作系统调度执行。
3、区别(纯属个人理解)
多线程中,每个线程实体都是一样的;多协程中,每个协程实体都是不一样的。
多线程中,内核决定那个线程被挂起和被恢复;多协程中,程序实现者(协程调度者)来决定那个协程被挂起和被恢复。
4、误区
中文绝大多数资料都显示,协程是轻量级的线程。我认为这句话表述有误,甚至完全是错误的。线程是在进程中,一个进程中有一个或多个线程,一个线程中有多个协程。但是,协程在线程中和线程在进程中是完全不一样的。内核会调度进程中的线程,但是内核不会调度线程中的协程。用户态无法调度线程,但用户态可以调度协程。
二、进程和协程实现并行的本质区别
对于单核处理器,多进程实现多任务的原理是让操作系统给一个进程(任务)每次分配一定的CPU时间片,然后中断、让下一个任务执行一定的时间片接着再中断并继续执行下一个,如此反复。由于切换执行任务的速度非常快,给外部用户的感受就是多个任务的执行是同时进行的。
多进程的调度是由操作系统来实现的,进程自身不能控制自己何时被调度,也就是说:进程的调度是由外层调度器抢占式实现的。
而协程要求当前正在运行的任务自动把控制权回传给调度器,这样就可以继续运行其他任务。这与抢占式的多任务正好相反, 抢占多任务的调度器可以强制中断正在运行的任务, 不管它自己有没有意愿。协程的调度是由协程自身主动让出控制权到外层调度器实现的。
三、协程的实现
0、进程是由操作系统调度的,所以创建一个进程很容易直接fork就可以了。但是协程是用户自己调度的,调度器需要自己实现。
第二部分 PHP中实现协程
了解PHP实现协程,我们需要首先弄懂PHP中的迭代器和生成器。可以参考本站之前的内容《PHP迭代器学习笔记》《PHP中生成器学习笔记》
注:简单理解迭代器和生成器都是用来遍历对象的,迭代器以类的形式表现,生成器以函数的形式表现。
一、通过生成器实现协程
前面说了协程需要用户自己调度任务,那么任务和调度器都需要用户自己实现。
0、任务类Task.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(); } }
1、调度器类Scheduler.php
include './Task.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); } } } }
2、协程测试Case类TestCoroutine.php
include './Scheduler.php'; 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();
以上运行结果
php TestCoroutine.php 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.
二、通过swoole实现协程
0、参考swoole官网Coroutine
参考:
风雪之隅:在PHP中使用协程实现多任务调度
SF:PHP7下的协程实现
PHP协程
PHP中的yield与协程(二十节)
PHP中的yield与协程(二十一节)