zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    从句分析
    artDialog ( v 6.0.2 ) content 参数引入页面 html 内容
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 11 盛最多水的容器
    Java实现 LeetCode 11 盛最多水的容器
  • 原文地址:https://www.cnblogs.com/php-linux/p/12780447.html
Copyright © 2011-2022 走看看