PHP协程(1):简略

2 篇文章 0 订阅

基本概念

援引几个博客上的话:

对于单核处理器,多进程实现多任务的原理是让操作系统给一个任务每次分配一定的 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写法直接变成同步的写法;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值