PHP 协程

协程

“协程”就是用户态的线程

要理解是什么是”用户态的线程”,必然就要先理解什么是”内核态的线程”。 内核态的线程是由操作系统来进行调度的,在切换线程上下文时,要先保存上一个线程的上下文,然后执行下一个线程,当条件满足时,切换回上一个线程,并恢复上下文。 协程也是如此,只不过,用户态的线程不是由操作系统来调度的,而是由程序员来调度的,是在用户态的 – 摘自连接描述

关于”用户态线程”,我们用个小例子来加深理解

我们有两个函数 task1,task2,我们来手动调度它们的执行顺序,比如在task1执行一半的时候去执行task2,两个或者多个函数之间交替执行(这就是协程的概念)。

我们来个正常的函数调用方式:

<?php

function task1()
{
    echo "task1函数 执行1\n";
    echo "task1函数 执行2\n";
}

function task2()
{
    echo "task2函数 执行1\n";
    echo "task2函数 执行2\n";
}

// 调度
task1();
task2();

可想而知,以上的输出肯定是:

task1函数 执行第1
task1函数 执行第2
task2函数 执行第1
task2函数 执行第2

但是我想在程序输出task1函数 执行1之后就输出task2函数 执行1怎么办?

这个时候 yield 就派上用场了,PHP里的协程是需要借助 yield 来完成的。记住,yield 不是协程,而是协程需要借助 yield 的特性来实现。

<?php

function task1()
{
    echo "task1函数 执行1\n";
    yield;
    echo "task1函数 执行2\n";
}

function task2()
{
    echo "task2函数 执行1\n";
    yield;
    echo "task2函数 执行2\n";
}

// 调度
$task1 = task1();  // 返回一个生成器
$task2 = task2();  // 返回一个生成器
$task1->current();
$task2->current();

以上输出:

task1函数 执行1
task2函数 执行1

很好,以上结果达到了我们的预期。但是怎么让函数里的代码往下执行呢?

调用生成器的next方法:

$task1->next();
$task2->next();

最后你将看到的输出结果是两个函数交替执行输出的:

task1函数 执行1
task2函数 执行1
task1函数 执行2
task2函数 执行2

小段总结

以上的代码实现可以抽象出两个概念,任务调度任务就是task函数,调度就是我们怎么去调用这些task函数

调度器和任务生成器

上一个小段总结里有两个概念叫任务调度,我们简单的封装个任务生成器和调度器

// 任务生成器
$createTask = (function () {
    $tasks = [];
    return function ($callback) use (&$tasks) {
        $task = [
            'task' => $callback(),
            'id' => count($tasks) + 1,
        ];
        array_push($tasks, $task);
        return $task;
    };
})();

// 调度器
function schedule($tasks)
{
    $first = [];
    while (!empty($tasks)) {
        $task = array_shift($tasks);
        if (!array_key_exists($task['id'], $first)) {
            $first[$task['id']] = true;
            $task['task']->current();
        } else {
            $task['task']->next();
        }
        if ($task['task']->valid()) {
            array_push($tasks, $task);
        }
    }
}

使用

$tasks = [
    $createTask(function () {
        echo "任务1 执行第1次\n";
        yield;
        echo "任务1 执行第2次\n";
    }),
    $createTask(function () {
        echo "任务2 执行第1次\n";
        yield;
        echo "任务2 执行第2次\n";
    })
];
schedule($tasks);

输出结果:

任务1 执行第1次
任务2 执行第1次
任务1 执行第2次
任务2 执行第2

可以从结果看出,调度器已经实现了多个任务之间进行协作。

网络请求

现在有个需求!就是任务在遇到网络请求的时候,我们无需等待网络请求的响应结果,而是遇到网络请求的时候,把这个任务挂起,然后去执行其它任务,等网络请求收到响应结果了再通知我们处理

这时候需要我们用到非阻塞IO调用相关技术,涉及到系统内核层面,想了解可以点击链接描述

在PHP里我们需要安装个扩展eio,大家自行安装

pecl install eio

编码:

$tasks = [
    $createTask(function () {
        echo "任务1 执行第1次\n";
        yield;
        echo "任务1 执行第2次\n";
    }),
    $createTask(function () {
        echo "任务2 执行第1次\n";
        eio_custom(function () {
            return file_get_contents('https://laravel-china.org');
        }, EIO_PRI_DEFAULT, function ($data, $ret) {
            echo "请求完成\n";
        });
        yield;
        echo "任务2 执行第2次\n";
    })
];

schedule($tasks);
eio_event_loop();

任务2 执行第1次的时候,遇到网络请求,我们把请求任务交给系统内核,然后切换到其它任务去,等请求任务完成后回调我们传入的函数。

输出结果:

任务1 执行第1次
任务2 执行第1次
任务1 执行第2次
任务2 执行第2次
任务2 执行第1次的请求完成

完!

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 7
falling-ts

闭包函数,应该也是协程实现一种方法吧

5年前 评论

又是一个标题党

5年前 评论

@yuanshang 我理解的是只要能实现,不管是闭包还是其它的方式都可以,像第一段说的“用户态线程” :grinning:

5年前 评论

实际使用还是 swoole 啦,可以看下它的文档哦。
https://wiki.swoole.com/wiki/page/p-corout...

5年前 评论

@韩槑槑 对的呢,这篇文章主要是想分享对协程的理解 :blush:

5年前 评论
// 任务生成器
$createTask = (function () {
    $tasks = [];
    return function ($callback) use (&$tasks) {
        $task = [
            'task' => $callback(),
            'id' => count($tasks) + 1,
        ];
        array_push($tasks, $task);
        return $task;
    };
})();

请问这种闭包是什么写法?我找了一圈没找到资料

3年前 评论

代码中unset($tasks[$k]);$k是什么,为什么要注销?

        if (!$task['task']->valid()) {
            unset($tasks[$k]);
        } else {
            array_push($tasks, $task);
        }
3年前 评论
朱其鹏 (楼主) 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!