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); // 任务如果还没完全执行完毕,入队等下次执行

}

}

}

}

这样我们基本就实现了一个协程调度器。

你可以使用下面的代码来测试:

<?php

function 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

回到顶部