zoukankan      html  css  js  c++  java
  • PHP协程

    协程

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

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

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

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

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

    <?php
    
    function task1()
    {
        echo "task1函数 执行1
    ";
        echo "task1函数 执行2
    ";
    }
    
    function task2()
    {
        echo "task2函数 执行1
    ";
        echo "task2函数 执行2
    ";
    }
    
    // 调度
    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
    ";
        yield;
        echo "task1函数 执行2
    ";
    }
    
    function task2()
    {
        echo "task2函数 执行1
    ";
        yield;
        echo "task2函数 执行2
    ";
    }
    
    // 调度
    $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()) {
                unset($tasks[$k]);
            } else {
                array_push($tasks, $task);
            }
        }
    }
    
    

    使用

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

    输出结果:

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

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

    网络请求

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

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

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

    ```pecl install eio ```

    编码:

    $tasks = [
        $createTask(function () {
            echo "任务1 执行第1次
    ";
            yield;
            echo "任务1 执行第2次
    ";
        }),
        $createTask(function () {
            echo "任务2 执行第1次
    ";
    
            eio_custom(function () {
                return file_get_contents('https://segmentfault.com/');
            }, EIO_PRI_DEFAULT, function ($data, $ret) {
                echo "请求完成
    ";
            });
            
            yield;
    
            echo "任务2 执行第2次
    ";
        })
    ];
    
    schedule($tasks);
    
    eio_event_loop();
    

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

    输出结果:

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

    完!

    原文地址:https://segmentfault.com/a/1190000016061073

  • 相关阅读:
    Java多线程——volatile关键字、发布和逸出
    线程安全性的基础知识
    maven web不能创建src/main/java等文件等问题
    web环境中的spring MVC
    Spring AOP 概述
    golang统计出其中英文字母、空格、数字和其它字符的个数
    go语言求1到100之内的质数
    golang fmt占位符
    golang---map类型
    golang切片类型
  • 原文地址:https://www.cnblogs.com/lalalagq/p/9974934.html
Copyright © 2011-2022 走看看