PHP协程机制进行多任务调度示例

在上一篇关于 PHP的迭代器、生成器和协程 的文章中,我们知道了,在PHP中可以通过使用yield关键字,把迭代器变成了生成器,然后通过它的让出/恢复机制,使用协程机制,在用户态进行调度。下面,我们用PHP协程机制来做一个多任务调度的示例。
1,任务执行Task
Task就是一个任务的抽象,协程就是用户空间线程,线程可以理解就是跑一个函数。
 所以Task的构造函数中就是接收一个闭包函数,我们命名为coroutine。
/** * Task任务类
 */
class Task
{
    protected $taskId;
    protected $coroutine;
    protected $beforeFirstYield = true; //是否在第一次yield之前
    protected $sendValue;
    /**
     * Task constructor.
     * @param $taskId
     * @param Generator $coroutine
     */
    public function __construct($taskId, Generator $coroutine)
    {
        $this->taskId = $taskId;
        $this->coroutine = $coroutine;
    }
    /**
     * 获取当前的Task的ID
     * 
     * @return mixed
     */
    public function getTaskId()
    {
        return $this->taskId;
    }
    /**
     * 判断Task执行完毕了没有
     * 
     * @return bool
     */
    public function isFinished()
    {
        return !$this->coroutine->valid();
    }
    /**
     * 设置下次要传给协程的值,比如 $id = (yield $xxxx),这个值就给了$id了
     * 
     * @param $value
     */
    public function setSendValue($value)
    {
        $this->sendValue = $value;
    }
    /**
     * 运行任务
     * 
     * @return mixed
     */
    public function run()
    {
        // 这里要注意,生成器的开始会reset,所以第一个值要用current获取
        if ($this->beforeFirstYield) {
            $this->beforeFirstYield = false;
            return $this->coroutine->current();
        } else {
            // 用send向生成器传参
            $retval = $this->coroutine->send($this->sendValue);
            $this->sendValue = null;
            return $retval;
        }
    }
}
2,任务调度Scheduler
接下来就是Scheduler这个重点核心部分,它扮演着调度员的角色。
<?php/**
 * Class Scheduler
 */
Class Scheduler
{
    /**
     * @var SplQueue
     */
    protected $taskQueue;
    /**
     * @var int
     */
    protected $tid = 0;
    /**
     * Scheduler constructor.
     */
    public function __construct()
    {
        /* 原理就是维护了一个队列,
         * 前面说过,从编程角度上看,协程的思想本质上就是控制流的主动让出(yield)和恢复(resume)机制
         */
        $this->taskQueue = new SplQueue();
    }
    /**
     * 增加一个任务
     *
     * @param Generator $task
     * @return int
     */
    public function addTask(Generator $task)
    {
        $tid = $this->tid;
        $task = new Task($tid, $task);
        $this->schedule($task);
        $this->tid++;
        return $tid;
    }
    /**
     * 把任务进入队列
     *
     * @param Task $task
     */
    public function schedule(Task $task)
    {
        $this->taskQueue->enqueue($task);
    }
    /**
     * 运行调度器
     */
    public function run()
    {
        while (!$this->taskQueue->isEmpty()) {
            // 任务出队
            $task = $this->taskQueue->dequeue();
            $res = $task->run(); // 运行任务直到 yield
            if (!$task->isFinished()) {
                $this->schedule($task); // 任务如果还没完全执行完毕,入队等下次执行
            }
        }
    }
}
这样我们基本就实现了一个协程调度器。
你可以使用下面的代码来测试:
<?phpfunction task1() {
    for ($i = 1; $i <= 10; ++$i) {
        echo "This is task 1 iteration $i.
";
        yield; // 主动让出CPU的执行权
    }
}
function task2() {
    for ($i = 1; $i <= 5; ++$i) {
        echo "This is task 2 iteration $i.
";
        yield; // 主动让出CPU的执行权
    }
}
$scheduler = new Scheduler; // 实例化一个调度器
$scheduler->addTask(task1()); // 添加不同的闭包函数作为任务
$scheduler->addTask(task2());
$scheduler->run();
那我们再进一步地来看看,哪里能用到协程。
function task1() {    /* 这是一个远程任务,需要耗时10s,可能是一个远程机器抓取分析远程网址的任务,我们只要提交最后去远程机器拿结果就行了 */
    remote_task_commit();
    // 上面的请求发出后,我们不要在这里等,通过yield保存数据现场,主动让出CPU的执行权给task2运行
    yield;
    //恢复数据现场,从远程机器拿结果,并通过yield把结果返回给调用方
    yield (remote_task_receive());
    ...
}
function task2() {
    for ($i = 1; $i <= 5; ++$i) {
        echo "This is task 2 iteration $i.
";
        yield; // 主动让出CPU的执行权
    }
}
这样就提高了程序的执行效率。
注意,如果在函数中使用了yield,就不能把这个函数当做普通函数来进行调用,如果你在一个协程函数中嵌套另外一个协程函数,则会导致调用不成功。这很容易理解,yield 需要进行数据现场的保存和恢复,如果嵌套了的话,则保存的和恢复的就会进行嵌套,导致错误。为了避免此种情况,需要使用 协程堆栈  这种方式,而 PHP7.0 推出了 yield from 去实现了 协程堆栈。
3,PHP7中yield from关键字
echoTimes函数
function echoTimes($msg, $max) {    for ($i = 1; $i <= $max; ++$i) {
        echo "$msg iteration $i
";
        yield;
    }
}
task1生成器
function task1(){
    yield from echoTimes("bar", 5);
}
这样,轻松调用子协程。
4,总结
至此,我们通过 PHP的关键字 yield ,生成器,send(),current(), valid() 等 实现了一套任务调度系统。并且知道,yield from 可以实现堆栈协程 / 子协程。当然,在实际工作中,我们可能不需要自己去实现这一套系统,因为 swoole 框架已经支持了协程机制,接下来,我们会去了解一下这个框架。
引用参考: https://segmentfault.com/a/1190000012457145?utm_source=tuicool&utm_medium=referral
以上是 PHP协程机制进行多任务调度示例 的全部内容, 来源链接: utcz.com/z/515638.html








