协程的执行顺序:

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. 理解Spring AOP的实现方式与思想

    Spring AOP简介 如果说IOC是Spring的核心,那么面向切面编程就是Spring最核心的功能之一了,在数据库事务中,面向切面编程被广泛应用. AOP能够将那些与业务无关,却为业务模块所共同 ...

  2. RocketMQ的发送模式和消费模式

    前言 小伙伴们大家好啊,王子又来和大家一起闲谈MQ技术了. 通过之前文章的学习,我们已经对RocketMQ的基本架构有了初步的了解,那今天王子就和大家一起来点实际的,用代码和大家一起看看RocketM ...

  3. python模块之----subprocess

    例子 >>> subprocess.getstatusoutput('pwd')(0, '/home/ronny')>>> subprocess.getoutput ...

  4. CTF-Bugku-杂项-21-28

    2020.09.14 下午奥力给 做题 第二十一题 细心的大象 https://ctf.bugku.com/challenges#细心的大象 这一看就是一只有故事的大象,图片详情中有东西,base64 ...

  5. 《C++深度解析》课程目录

    <C++深度解析>课程目录 第1课 - 学习 C++ 的意义 第2课 - C到C++的升级     第3课 - 进化后的const分析 第4课 - 布尔类型和引用 第5课 - 引用的本质分 ...

  6. [LeetCode]面试题67. 把字符串转换成整数

    题目 写一个函数 StrToInt,实现把字符串转换成整数这个功能.不能使用 atoi 或者其他类似的库函数. 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止. 当我们 ...

  7. java 多线程-4

    十四.sleep方法和wait方法的区别 [面试题] 相同点: 一旦执行方法,都可以使得当前线程进入阻塞状态. 不同点: 两个方法的声明位置不同:Thread类声明sleep():Object类中声明 ...

  8. springcloud一些博文记录

    自己搭建完整demo后看的一个系列博文https://my.oschina.net/u/3375733/following 现在看着这个系列的后期文章估计出差前不会看了 一个博主的系列的git地址和博 ...

  9. 跟我一起学Redis之Redis概述

    背景 技术的更新迭代,是程序员最最最头大的事,总是在每个网络角落中有感慨声:学不动啦: 其实新技术并不是凭空而出,而是随着业务推进.数据驱动.技术积累促使开发者的不断探索和实践,最终横空出世--&qu ...

  10. 每天一个dos命令-net.

    Rem:关于net命令相关的常用实例(如果cmd中执行net相关命令,报错:Access is denied. 可以右键cmd,以管理员身份运行即可!) 1.创建一个新账号:net user ifsf ...