• Swoole从入门到入土(8)——协程初探


    这一章节“协程”话题的讨论是为了让我们对之后协程风格服务端有更全面的了解。所以我们需要先一起了解一下什么是协程?协程有什么作用?

    当大家第一次看到“协程”这个词的时候,应该都一样会打开某度、某歌搜索一翻,然后搜到一堆很玄幻的概念,比如以下这一句:“协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。”往往让人看了一脸问号-_-

    其实“协程”这东西,我们可以这么理解,进程是个容器,对应具体干活的是线程;同样的道理,我们可以把线程看成是一个容器,协程是线程中具体干活的过程。换种说法,可以把协程想象成一个个函数(每个函数的作用不一样),而协程就是在同一个时间段内让这些函数同时工作。

    协程最主要的作用是:让原来要使用异步+回调方式写的非人类代码,可以用看似同步的方式写出来(即:按串行模型去组织原本分散在不同上下文中的代码逻辑)。

    网上有一些观点,说协程可以很好解决IO瓶颈问题(因为这时线程发挥不了作用)。编者只想说, IO 是系统调用,这个 IO 不是用户态能处理的,协程 是没办法绕开的,所以最终还是给堵了。如果 协程 真的能处理堵塞问题,那么很多经典的 Unix 网络编程书籍里面应该有 多协程 模式才对。

    编者作为IT界的泥石流,习惯了异步的写法,对于协程抱着接受但是审视的态度。今天我们就一起看看swoole下的协编程。

    我们先看看一段简易的协程代码:

    echo "main start",PHP_EOL;
    Co
    un(function(){
        echo "co 1 start",PHP_EOL;
    
        go(function(){
            echo "co 2 start",PHP_EOL;echo "co 2 end",PHP_EOL;
        });
    
        go(function(){
            echo "co 3 start",PHP_EOL;echo "co 3 end",PHP_EOL;
        });
    
        echo "co 1 end",PHP_EOL;
    });
    echo "end";

    这段代码的执行结果是:

    main  start

    co 1 start

    co 2 start

    co 2 end

    co 3 start

    co 3 end

    co 1 end

    end

    好像也没什么嘛,就是代码顺序执行的结果。先不要急,我们一起看看代码中比较陌生的部分:

    Co un():在Swoole直接裸写协程启动,就需要调用这个函数(其实是对 SwooleCoroutineScheduler 类 (协程调度器类) 的封装),可以理解为C语言里的main()函数。(另外,Swoole 提供的 2 个进程管理模块 Process 和 ProcessPool 的 start 方法,此种启动方式会在进程启动的时候创建协程容器,参考这两个模块构造函数的 enable_coroutine 参数)。

    go():添加一个子协程。

    现在,我们把代码修改如下(添加了Co::sleep)

    echo "main start",PHP_EOL;
    Co
    un(function(){
        echo "co 1 start",PHP_EOL;
    
        go(function(){
            echo "co 2 start",PHP_EOL;
            co::sleep(1);
            echo "co 2 end",PHP_EOL;
        });
    
        go(function(){
            echo "co 3 start",PHP_EOL;
            co::sleep(.5);
            echo "co 3 end",PHP_EOL;
        });
    
        echo "co 1 end",PHP_EOL;
    });
    echo "end";

    这时的结果会变成:

    main start

    co 1 start

    co 2 start

    co 3 start

    co 1 end

    co 3 end

    co 2 end

    end

    是不是很神奇:)根据等待的时间,协程会自动调度现在可以马上就处理的代码片断。嗯,这就是协程最大的优势。

    我们再做一个实验,把上面的co::sleep()函数换成php自带的sleep()函数会发生什么事?

    echo "main start",PHP_EOL;
    Co
    un(function(){
        echo "co 1 start",PHP_EOL;
    
        go(function(){
            echo "co 2 start",PHP_EOL;
            sleep(1);    //注意这里
            echo "co 2 end",PHP_EOL;
        });
    
        go(function(){
            echo "co 3 start",PHP_EOL;
            sleep(3);    //注意这里
            echo "co 3 end",PHP_EOL;
        });
    
        echo "co 1 end",PHP_EOL;
    });
    echo "end";

    这时,我们得到的结果同样会是:

    main  start

    co 1 start

    co 2 start

    co 2 end

    co 3 start

    co 3 end

    co 1 end

    end

    区别在于在co 2 start和co 3 start会被阻塞等待。究其原因就是在co::sleep()内部会用yield把时间片让出来,而sleep()则是在系统层面等待。这就是说为什么系统IO是协程绕不开的原因,该等的还是得等,只不过用了一些技巧是在其它地方等而已。

    这就给了我们一些启发,如果用协程编程,碰到需要阻塞的部分(比如sleep、http请求、mysql连接、文件读写),需要用swoole为我们提供的现在协程库;如果协程库不够用,则利用异步原理与协程库实现自己的协程组件。

    但是,swoole已经考虑到程序员中不乏杠精,就想把上面这请阻塞操作变成协程,只需如下操作就可以实现把代码“一键协程化”:

    SwooleRuntime::enableCoroutine();    //重点在这一句
    echo "main start",PHP_EOL;
    Co
    un(function(){
        echo "co 1 start",PHP_EOL;
    
        go(function(){
            echo "co 2 start",PHP_EOL;
            sleep(2);
            echo "co 2 end",PHP_EOL;
        });
    
        go(function(){
            echo "co 3 start",PHP_EOL;
            sleep(1);
            echo "co 3 end",PHP_EOL;
        });
    
        echo "co 1 end",PHP_EOL;
    });
    echo "end";

    虽然swoole提供了“一键协程化”的神仙操作,可以把文件操作,sleep,Mysqli,PDO,streams等都变成异步IO,但是要注意,底层是使用了HOOK的方式把原PHP的代码调用转移到了swoole的函数内,可参考这里。所以认识swoole提供的原生协程库也是非常重要的。

    要注意的是:SwooleRuntime::enableCoroutine(),并不针对curl。如果想要把curl也协程化,需要调用SwooleRuntime::enableCoroutine($flags = SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL); 对于Co::set(['hook_flags' => SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL]); 也是一样。

    接下来,再来一段代码:

    echo "main start",PHP_EOL;
    echo "co 1 start",PHP_EOL;
    
    go(function(){
        echo "co 2 start",PHP_EOL;
        co::sleep(1);
        echo "co 2 end",PHP_EOL;
    });
    
    go(function(){
        echo "co 3 start",PHP_EOL;
        co::sleep(.5);
        echo "co 3 end",PHP_EOL;
    });
    
    echo "co 1 end",PHP_EOL;
    
    echo "end",PHP_EOL;

    我们得到的结果是:

    main start

    co 1 start

    co 2 start

    co 3 start

    co 1 end

    end

    co 2 end

    co 2 end

    在这一段代码中,没有协程容器Co un()的存在,go()子协程同样会被调用。可以看出协程容器可以保证容器内的协程代码全部执行完成后,再跳出容器往下执行外部的代码。

    好了,协程初探就先到这里了。接下去,我们会先了解协程版本的TCP服务器。关于swoole协程的其它话题,我们后续会进行讨论。

    ---------------------------  我是可爱的分割线  ----------------------------

    最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。

  • 相关阅读:
    JaveScript简单数据类型(JS知识点归纳二)
    JaveScript变量的简介及其变量的简单使用(JS知识点归纳一)
    JaveScript用二分法与普通遍历(冒泡)
    include、include_once、require、require_once其区别
    POST和GET有什么区别?
    前端向后台发送请求有几种方式?
    jQuery的$.ajax方法响应数据类型有哪几种?本质上原生ajax响应数据格式有哪几种,分别对应哪个属性?
    java根据汉字生成首字母大写
    springboot+使用切面AOP动态获取自定义注解
    JavaScript基础05——严格模式
  • 原文地址:https://www.cnblogs.com/ddcoder/p/13762402.html
Copyright © 2020-2023  润新知