鼠…鼠标放错地方了!

 协程的执行顺序:
go(function () {
    echo "hello go1 \n";
});
 
echo "hello main \n";
 
go(function () {
    echo "hello go2 \n";
});

 

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

备注:\Swoole\Coroutine 可以简写为 \Co

上面的代码执行结果:

# php co.php
hello go1
hello main
hello go2

 

实际执行过程:

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

下面稍微改一下执行顺序

use Co;
 
go(function () {
    Co::sleep(1); // 只新增了一行代码
    echo "hello go1 \n";
});
 
echo "hello main \n";
 
go(function () {
    echo "hello go2 \n";
});

 

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

# 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个任务

$n = 4;
for ($i = 0; $i < $n; $i++) {
    sleep(1);
    echo microtime(true) . ": hello $i \n";
};
echo "hello main \n";

 

执行结果:

# 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

 

单个协程版:

$n = 4;
go(function () use ($n) {
    for ($i = 0; $i < $n; $i++) {
        Co::sleep(1);
        echo microtime(true) . ": hello $i \n";
    };
});
echo "hello main \n";

 

执行结果:

# 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

 

多协程版本:

$n = 4;
for ($i = 0; $i < $n; $i++) {
    go(function () use ($i) {
        Co::sleep(1);
        echo microtime(true) . ": hello $i \n";
    });
};
echo "hello main \n";

 

执行结果:

# 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密集型

$n = 4;
for ($i = 0; $i < $n; $i++) {
    go(function () use ($i) {
        // Co::sleep(1);
        sleep(1);
        echo microtime(true) . ": hello $i \n";
    });
};
echo "hello main \n";

 

执行的结果:

# 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:

// 同步版, 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 Co\Redis();
        $redis->connect('redis', 6379);
        $redis->auth('123');
        $redis->get('key');
    }
});
 
// 多协程版, 真正使用到协程调度带来的 IO 阻塞时的调度
for ($i = 0; $i < $cnt; $i++) {
    go(function () {
        $redis = new Co\Redis();
        $redis->connect('redis', 6379);
        $redis->auth('123');
        $redis->get('key');
    });
}

 

性能对比:

# 多协程版
# 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 多线程

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    go func() {
        fmt.Println("hello go")
    }()
 
    fmt.Println("hello main")
 
    time.Sleep(time.Second)
}

 

执行结果:

$ go run test.go
hello main
hello go

 

原文链接

 posted on 2020-07-31 20:48  xiao韩de博客  阅读(231)  评论(0编辑  收藏  举报