协程的执行顺序:

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. [04] C# Alloc Free编程之实践

    C# Alloc Free编程之实践 上一篇说了Alloc Free编程的基本理论. 这篇文章就说怎么具体做实践. 常识 之所以说是常识, 那是因为我们在学任何一门语言的时候, 都能在各种书上看到各种 ...

  2. oracle之序列

    序列 15.1 序列是生成唯一整数值的结构,它的典型用途是用于主键值. 结合真题演示伪列nextval, currval用法 CREATE SEQUENCE dept_deptnoINCREMENT ...

  3. C++ Templates (2.3 类模板的局部使用 Partial Usage of Class Templates)

    返回完整目录 目录 2.3 类模板的局部使用 Partial Usage of Class Templates 2.3.1 Concepts 2.3 类模板的局部使用 Partial Usage of ...

  4. [LeetCode]347. 前 K 个高频元素(堆)

    题目 给定一个非空的整数数组,返回其中出现频率前 k 高的元素. 示例 1: 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2] 示例 2: 输入: nums = [1 ...

  5. [程序员代码面试指南]递归和动态规划-数字字符串转换为字母组合的种数(DP)

    题意 给一个字符串,只由数字组成,若是'1'-'26',则认为可以转换为'a'-'z'对应的字母,问有多少种转换方法. 题解 状态转移很好想,注意dp多开一位,dp[0]为dp[2]的计算做准备.dp ...

  6. JVM初认识

    运行时数据区域 程序计数器:程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节 ...

  7. 【python】调用sm.ms图床api接口,实现上传图片并返回url

    图床简介 sm.ms网站提供免费的图床服务.单图上传大小限制5MB,每次最多上传10张,支持多种图片链接格式和api接口调用. 获取令牌 注册账号并登录,点击User-Dashboard 点击API ...

  8. java学习(九) —— java中的File文件操作及IO流概述

    前言 流是干什么的:为了永久性的保存数据. IO流用来处理设备之间的数据传输(上传和下载文件) java对数据的操作是通过流的方式. java用于操作流的对象都在IO包中. java IO系统的学习, ...

  9. 9.Lock-锁

  10. FTP服务端 FTP服务端搭建教程

    FTP服务端搭建教程如下:一.需要准备以下工具:1.微型FTP服务端.2.服务器管理工具二.操作步骤:1.下载微型FTP服务端.(站长工具包可下载:http://zzgjb.iis7.com/ )2. ...