PHP中协程实现学习笔记

第一部分 什么是协程(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与协程(二十一节)

发表评论

电子邮件地址不会被公开。 必填项已用*标注