# 协程
协程说复杂不复杂说难也不难,一句话可以概括:能提高并发,但不能加速任务,同步代码实现异步IO,异步非阻塞的代码块。
协程是一种特殊函数,是一种可以挂起的函数,然后可以从挂起的地方重新恢复执行,一个线程内的多个协程是串行的,跟CPU处理进程一样,同一时刻只能一个协程在线程上运行,除非出让了控制权给别的协程运行。协程无法利用多核CPU因此协程只能解决并发问题,不能解决任务处理速度问题。协程就是把一个大任务再分成更小的片段,封装程一个函数,当其中一个协程需要IO阻塞的时候,主动挂起当前协程,把控制权交给其他协程运行。
我们知道进程和线程是由操作系统调度的,什么时候执行取决于操作系统什么时候把CPU时间交给某个进程或者线程,而协程是什么时候交出控制权是由用户决定的。进程和线程属于内核态,协程属于用户态线程。
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
### 协程特点
* 1、用户态线程、遇到IO主动让出控制权
* 2、多个协程代码依然是串行的,无需加锁
* 3、开销低,只占用内存,不存在进程、线程切换开销
* 4、并发量大,单个进程可开启50w个协程
* 5、随时随地,只要想并发,就调用go创建协程
![](https://img.kancloud.cn/bc/b1/bcb155964abee12726c52719877382af_754x645.png)
<center>图:1.5-1</center>
我们知道线程是轻量级的进程,那么协程就是轻量级的线程。协程运行在线程之上,一个线程可以有多个协程。
我们知道在进程遇到阻塞的时候开多一个线程在进程内部切换,避免每次都切换进程,这样可以更大力度的使用CPU分给这个进程的可使用时间。而协程跟线程和进程的关系很类似,只不过协程是跟线程直接建立关系。
![](https://img.kancloud.cn/b3/02/b30265b2e38f39227518b6fd1b890317_2000x656.png)
<center>图:1.5-2</center>
图:1.5-2 是多个线程之间切换的示意图,那么我们来考虑一下,如果线程只是等待IO操作(网络或者文件),那么为什么像线程重复使用进程一样来重复的使用这个线程呢?我们把IO去掉,看看这个图是什么样子的。
![](https://img.kancloud.cn/32/1a/321a9e35422fbefb09de47a56ae4e4ae_1504x494.png)
<center>图:1.5-3</center>
去掉IO部分操作,可以看出来基本上这个并发请求应用程序代码可以在 `单个线程中` 运行,协程最大力度的利用了线程等待IO的时间,让程序在等待IO的时候可以执行别的业务代码。
![](https://img.kancloud.cn/10/43/1043f5ab17933d4b062124a6eddba1f2_1176x632.png)
<center>图:1.5-4</center>
从图:1.5-4 看着像不像一个线程的执行流程,这就是协程的魅力所在,当一个协程被yield之后会被挂起,把控制权转移给线程内部的其他协程,因为是在线程上进行的切换,所以开销远远比进程和线程低很多。
![](https://img.kancloud.cn/1c/60/1c60d21a3d4072e840819d7c7272fdfa_2028x878.png)
<center>图:1.5-5</center>
当程序调用协程之后,当前协程会主动让出控制权交给同一个线程内的其他协程处理,类似图:1.5-5所示,开发者代码中需要使用IO的时候主动让出协程的控制权给别的协程使用。
![](https://img.kancloud.cn/18/5f/185fd66abb2c80d129b503b2eb444b31_1834x806.png)
<center>图:1.5-6</center>
去掉IO部分再看协程的处理,就跟图:1.5-4所示的一样,直接执行的都是业务逻辑,避免遇到IO导致线程转换到等待状态,更充分的利用CPU分给这个线程的执行时间。
> 注意:协程并不能让任务加速进行,只能执行更多任务。
协程由于是建立在线程之上的,因此没有办法使用CPU多核心的优势,协程适合适用于IO密集运算的场景。
### 协程有什么作用?
协程是为了提高CPU使用率,避免在线程阻塞的时候大量的线程上下文切换。
```php
echo "1-start\n";
sleep(1);
echo "1-end\n";
echo "2-start\n";
sleep(1);
echo "2-end\n";
echo "3-start\n";
sleep(1);
echo "3-end\n";
echo "4-start\n";
sleep(1);
echo "4-end\n";
```
![](https://img.kancloud.cn/ea/7e/ea7eee09bcb6113549793ba49e7de26b_574x207.png)
<center>图:1.5-7</center>
以上代码的CPU使用率仅有`1%`
```php
Swoole\Runtime::enableCoroutine(true);
go(function () {
echo "go1-start\n";
sleep(1);
echo "go1-end\n";
});
go(function () {
echo "go2-start\n";
sleep(1);
echo "go2-end\n";
});
go(function () {
echo "go3-start\n";
sleep(1);
echo "go3-end\n";
});
go(function () {
echo "go4-start\n";
sleep(1);
echo "go4-end\n";
});
```
![](https://img.kancloud.cn/62/b1/62b16c26d4edf3497a3f591797a9362a_574x206.png)
<center>图:1.5-8</center>
使用协程,成功把CPU使用率提高到了`4%`,这样CPU就不需要为了IO阻塞而空跑,或者进行上下文切换。之前不是说过协程不能加速吗?这里使用协程之后怎么`1秒`多就执行完了,跟前面的代码不一样?这里得到的时间取决于最后一个协程执行结束的时间。
### 协程的执行顺序
```php
Swoole\Runtime::enableCoroutine(true);
go(function(){
sleep(2);
echo "go1\n";
});
go(function(){
sleep(1);
echo "go2\n";
});
echo "main\n";
```
先输出:main->go2->go1
### 协程之间通讯
多个协程之间通讯,采用Channel实现,多个协程协助完成共同的任务。
```php
Swoole\Runtime::enableCoroutine(true);
$chan = new Swoole\Coroutine\Channel();
go(function () use ($chan){
sleep(1);
$chan->push(['name'=>'sunny']);
});
go(function() use ($chan){
$data = $chan->pop();
print_r($data);
});
echo "结束\n";
```
- 第一章:基础知识
- 课程简介
- PHP-FPM过渡常驻内存
- 进程
- 实战:实现Master-Worker
- 线程
- 实战:CC攻击器
- 协程
- 实战:实现waitGroup功能
- 进程、线程、协程的区别
- 第二章:初识Swoft2.0
- Swoft介绍
- Swoft环境安装
- gcc升级
- 安装Swoft框架
- 目录结构介绍
- SwoftCli工具
- Swoft配置
- 第三章:Swoft2.0核心
- 上下文
- 常驻内存没有上下文隔离
- 实战:手写swoole框架上下文管理
- Bean容器
- 实战:根据容器原理实现容器
- 实战:通过容器实现依赖注入
- Bean容器定义与使用
- 配置文件定义Bean
- 容器类型
- 面向接口的容器
- 注解
- 实战:实现注解
- 自定义Swoft注解类
- 事件
- 连接池
- 实战:Swoole实现连接池
- 第四章:Http服务器
- Http Server生命周期
- Http Server配置
- 控制器
- 路由
- 请求对象Request
- 响应对象Response
- Http异常处理
- 中间件
- 实战:中间件实现JWT登陆授权
- 第五章:验证器
- 内置验证类型
- 验证器的使用
- 自定义验证器
- 第六章:数据库操作
- 连接数据库
- 实体模型
- 模型事件
- 查询器
- 事务处理
- 连接池配置
- 读写分离
- 多数据库切换
- Models分层结构
- 实战:实现用户CURD API
- 第七章:Redis
- 连接redis和使用
- Redis连接池
- Redis集群配置(单机版)
- Redis集群配置(多服务器)
- Redis连接集群
- Redis实战:实现延时任务
- 第八章:AOP编程
- AOP概念
- AOP实现原理
- 实战实现AOP:静态代理
- 实战实现AOP:动态代理
- 切面注解介绍
- PointExecution切面
- PointBean切面
- PointAnnotation切面
- 实战:使用AOP实现日志记录
- 第九章:任务处理
- 进程使用
- 进程池使用
- 实战:进程消费队列
- 实战:进程实现RabbitMQ延时队列
- 异步任务
- 协程任务
- 定时任务