• swoole 协程介绍


     

    协程的执行顺序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    go(function () {
        echo "hello go1 ";
    });
     
    echo "hello main ";
     
    go(function () {
        echo "hello go2 ";
    });

    go() 是 Co::create() 的缩写,用来创建一个协程,接受callback作为参数,callback中的代码。会在这个新建的协程中执行。

    备注:SwooleCoroutine 可以简写为 Co

    上面的代码执行结果:

    1
    2
    3
    4
    # php co.php
    hello go1
    hello main
    hello go2

    实际执行过程:

    • 运行此段代码,系统启动一个新进程
    • 遇到 go() ,当前进程中生成一个协程,协程中输出 hello go1,协程退出
    • 进程继续向下执行代码,输出 hello main 
    • 再生成一个协程,协程中输出 hello go2,协程退出

    下面稍微改一下执行顺序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    use Co;
     
    go(function () {
        Co::sleep(1); // 只新增了一行代码
        echo "hello go1 ";
    });
     
    echo "hello main ";
     
    go(function () {
        echo "hello go2 ";
    });

    Co::sleep() 函数功能和 sleep() 差不多,但是它模拟的是IO等待,执行的顺序如下:

    1
    2
    3
    4
    # php co.php
    hello main
    hello go2
    hello go1

    上面的实际执行过程如下:

    • 运行此段代码,系统启动一个进程
    • 遇到 go(),当前进程中生成一个协程
    • 协程中遇到IO阻塞(这里是 Co::sleep() 模拟出来的IO等待),协程让出控制,进入协程调度队列
    • 进程继续向下执行,输出 hello main
    • 执行下一个协程,输出 hello go2
    • 之前的协程准备就绪,继续执行,输出 hello go1

    协程快在哪?减少IO阻塞导致的性能损失

    一般的计算机任务分为两种:

    • CPU密集型,比如加减乘除等科学计算
    • IO密集型,比如网络请求,文件读写等

    高性能相关的两个概念:

    • 并行:同一个时刻,同一个CPU只能执行同一个任务,要同时执行多个任务,就需要有多个CPU才行
    • 并发:由于CPU切换任务非常快,所以让人感觉像是有多个任务同时执行

    协程适合的场景是IO密集型应用,因为协程在IO阻塞时会自动调度,减少IO阻塞导致的时间损失。

    普通版:执行4个任务

    1
    2
    3
    4
    5
    6
    $n = 4;
    for ($i = 0; $i $n$i++) {
        sleep(1);
        echo microtime(true) . ": hello $i ";
    };
    echo "hello main ";

    执行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # php co.php
    1528965075.4608: hello 0
    1528965076.461: hello 1
    1528965077.4613: hello 2
    1528965078.4616: hello 3
    hello main
    real    0m 4.02s
    user    0m 0.01s
    sys     0m 0.00s

    单个协程版:

    1
    2
    3
    4
    5
    6
    7
    8
    $n = 4;
    go(function () use ($n) {
        for ($i = 0; $i $n$i++) {
            Co::sleep(1);
            echo microtime(true) . ": hello $i ";
        };
    });
    echo "hello main ";

    执行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # php co.php
    hello main
    1528965150.4834: hello 0
    1528965151.4846: hello 1
    1528965152.4859: hello 2
    1528965153.4872: hello 3
    real    0m 4.03s
    user    0m 0.00s
    sys     0m 0.02s

    多协程版本:

    1
    2
    3
    4
    5
    6
    7
    8
    $n = 4;
    for ($i = 0; $i $n$i++) {
        go(function () use ($i) {
            Co::sleep(1);
            echo microtime(true) . ": hello $i ";
        });
    };
    echo "hello main ";

    执行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # php co.php
    hello main
    1528965245.5491: hello 0
    1528965245.5498: hello 3
    1528965245.5502: hello 2
    1528965245.5506: hello 1
    real    0m 1.02s
    user    0m 0.01s
    sys     0m 0.00s

    这三种版本为什么时间上有很大的差异?

    • 普通版本:会遇到IO阻塞,导致的性能损失
    • 单协程版本:尽管IO阻塞引发了协程调度,但当前只有一个协程,调度之后还是执行当前协程
    • 多协程版本:真正发挥出协程的优势,遇到IO阻塞时发生调度,IO就绪时恢复运行

    下面将多协程版本修改为CPU密集型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $n = 4;
    for ($i = 0; $i $n$i++) {
        go(function () use ($i) {
            // Co::sleep(1);
            sleep(1);
            echo microtime(true) . ": hello $i ";
        });
    };
    echo "hello main ";

    执行的结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # php co.php
    1528965743.4327: hello 0
    1528965744.4331: hello 1
    1528965745.4337: hello 2
    1528965746.4342: hello 3
    hello main
    real    0m 4.02s
    user    0m 0.01s
    sys     0m 0.00s

    只是将 Co::sleep() 改成了sleep() ,时间又和普通版本差不多,原因是:

    • sleep() 可以看做是CPU密集型任务,不会引起协程的调度
    • Co::sleep() 模拟的是IO密集型任务,会引发协程的调度

    这就是为什么协程适合IO密集型应用。

    下面使用一组对比,使用redis:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 同步版, redis使用时会有 IO 阻塞
    $cnt = 2000;
    for ($i = 0; $i $cnt$i++) {
        $redis new Redis();
        $redis->connect('redis');
        $redis->auth('123');
        $key $redis->get('key');
    }
     
    // 单协程版: 只有一个协程, 并没有使用到协程调度减少 IO 阻塞
    go(function () use ($cnt) {
        for ($i = 0; $i $cnt$i++) {
            $redis new CoRedis();
            $redis->connect('redis', 6379);
            $redis->auth('123');
            $redis->get('key');
        }
    });
     
    // 多协程版, 真正使用到协程调度带来的 IO 阻塞时的调度
    for ($i = 0; $i $cnt$i++) {
        go(function () {
            $redis new CoRedis();
            $redis->connect('redis', 6379);
            $redis->auth('123');
            $redis->get('key');
        });
    }

    性能对比:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 多协程版
    # php co.php
    real    0m 0.54s
    user    0m 0.04s
    sys     0m 0.23s
     
    # 同步版
    # php co.php
    real    0m 1.48s
    user    0m 0.17s
    sys     0m 0.57s

    swoole协程和go协程对比:单进程 VS 多线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package main
     
    import (
        "fmt"
        "time"
    )
     
    func main() {
        go func() {
            fmt.Println("hello go")
        }()
     
        fmt.Println("hello main")
     
        time.Sleep(time.Second)
    }

    执行结果:

    1
    2
    3
    go run test.go
    hello main
    hello go

    go代码的执行过程如下:

    • 运行 go 代码,系统启动一个新进程
    • 查找 package main ,然后执行其中的 func main()
    • 遇到协程,交给协程调度器执行
    • 继续向下执行,输出 hello main 
    • 如果不添加 time.Sleep(time.Second),main函数执行完,程序结束,进程退出,导致调度中的协程也终止

    swoole和go实现协程调度的模型不同,go中使用的是MPG模型:

    • M 指的是 Machine, 一个M直接关联了一个内核线程
    • P 指的是 processor, 代表了M所需的上下文环境, 也是处理用户级代码逻辑的处理器
    • G 指的是 Goroutine, 其实本质上也是一种轻量级的线程

    而swoole中的协程调度使用单进程模型,所有协程都是在当前进程中进行调度,单进程的好处是:简单 / 不用加锁 / 性能高。

     https://www.cnblogs.com/xi-jie/articles/10466610.html

  • 相关阅读:
    强大的mono.cecil
    关于svn不能cleanup的问题
    SVN项目,快速查看项目的当前版本号
    jQuery选择器总结
    将Excel数据导入mysql数据库的几种方法
    SpringMVC表单标签简介
    mybatis动态SQL语句
    mysql时间格式化,按时间段查询MYSQL语句
    深入了解ios系统机制
    Eclipse 官方简体中文语言包下载地址及安装方法
  • 原文地址:https://www.cnblogs.com/brady-wang/p/12780447.html
Copyright © 2020-2023  润新知