协程的执行顺序:

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

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

备注:\Swoole\Coroutine 可以简写为 \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 \n";
});
 
echo "hello main \n";
 
go(function () {
    echo "hello go2 \n";
});

\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 \n";
};
echo "hello main \n";

执行结果:

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 \n";
    };
});
echo "hello main \n";

执行结果:

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 \n";
    });
};
echo "hello main \n";

执行结果:

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 \n";
    });
};
echo "hello main \n";

执行的结果:

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 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');
    });
}

性能对比:

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

swoole 协程介绍的更多相关文章

  1. [转]Unity3D协程介绍 以及 使用

    作者ChevyRay ,2013年9月28日,snaker7译  原文地址:http://unitypatterns.com/introduction-to-coroutines/ 在Unity中,协 ...

  2. [Sw] Swoole-4.2.9 可以尝试愉快应用 Swoole 协程

    大家知道 Swoole 提供了方便于服务器.网络编程的模式,简化了多进程编程. 这直接让 PHP 的运行很容易变成常驻内存的 Server 程序,执行效率上有了数倍的提升. 但是这一切还没有让人足够兴 ...

  3. python 全栈开发,Day43(引子,协程介绍,Greenlet模块,Gevent模块,Gevent之同步与异步)

    昨日内容回顾 I/O模型,面试会问到I/O操作,不占用CPU.它内部有一个专门的处理I/O模块.print和写log 属于I/O操作,它不占用CPU 线程GIL保证一个进程中的多个线程在同一时刻只有一 ...

  4. {python之协程}一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二

    python之协程 阅读目录 一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二 一 引子 本 ...

  5. Unity3D协程介绍 以及 使用

    作者ChevyRay ,2013年9月28日,snaker7译  原文地址:http://unitypatterns.com/introduction-to-coroutines/ 在Unity中,协 ...

  6. 【python】-- 协程介绍及基本示例、协程遇到IO操作自动切换、协程(gevent)并发爬网页

    协程介绍及基本示例 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是协程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他 ...

  7. Swoole 协程 MySQL 客户端与异步回调 MySQL 客户端的对比

    Swoole 协程 MySql 客户端与 异步回调 MySql 客户端的对比 为什么要对比这两种不同模式的客户端? 异步 MySQL 回调客户端是虽然在 Swoole 1.8.6 版本就已经发布了, ...

  8. Swoole 协程与 Go 协程的区别

    Swoole 协程与 Go 协程的区别 进程.线程.协程的概念 进程是什么? 进程就是应用程序的启动实例. 例如:打开一个软件,就是开启了一个进程. 进程拥有代码和打开的文件资源,数据资源,独立的内存 ...

  9. Swoole协程与传统fpm同步模式比较

    如果说数组是 PHP 的精髓,数组玩得不6的,根本不能算是会用PHP.那协程对于 Swoole 也是同理,不理解协程去用 Swoole,那就是在瞎用. 首先,Swoole 只能运行在命令行(Cli)模 ...

随机推荐

  1. Java数据类型之Cache模式

    1.关于Java数据类型 基本数据类型 基本数据类型有8种,每种基本数据类型都有对应的引用类型. 类型 描述 长度 可表示数据 包装类型 boolean 布尔型 1 true.false Boolea ...

  2. unzip命令笔记

    unzip命令 文件压缩与解压 unzip命令用于解压缩由zip命令压缩的".zip"压缩包. 语法 unzip(选项)(参数) 选项 -c:将解压缩的结果显示到屏幕上,并对字符做 ...

  3. TNFE-Weekly[第七十二周已更新]

    前端行业发展飞速,新技术如雨后春笋般快速出现,尤其是各种小程序陆续推出,相关的信息.文章也铺天盖地的遍布在各处,我们有时候会困惑,不知道哪些信息对于自己是有价值的,那么TNFE-腾讯新闻前端团队启动了 ...

  4. python3 变量

    python 3变量名不能以数字开头但能数字结尾 变量名大小写敏感 在多个单词组成的变量名中以下划线间隔

  5. 刀哥多线程自动释放池autoreleasepool

    自动释放池 作用 自动释放对象的 所有 autorelease 的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中 自动释放池被销毁或者耗尽时,会向池中所有对象发送 release 消息, ...

  6. 常用的Websocket技术一览

    1. 前言 Websocket是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议.WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据 ...

  7. 2.BlockingQueue-阻塞式队列

  8. Do not use built-in or reserved HTML elements as component id:mask vue报错

    今天学习了一下vue的组件,但是报了一个错误 Do not use built-in or reserved HTML elements as component id:mask , 经过查询得知是因 ...

  9. Java10新特性

    局部变量的类型推断 这个功能减少与编写Java相关的冗长度,同时保持对静态类型安全性的承诺 使用举例 public static void main(String[] args) { // 使用var ...

  10. Spring框架分为哪七大模块,各模块的主要功能作用是什么

    七大模块,如下: 1. Spring Core: Core封装包是框架的最基础部分,提供IOC和依赖注入特性.这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性 ...