[TOC]
# 协程
******
协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
![](https://img.kancloud.cn/27/9a/279a8118adade9f8a59a800e9babbd32_725x1198.png)
<br />
## 协程执行顺序
首先,我们来看一个原生 php 代码:
```php
<?php
function task1() {
for ($i = 0; $i <= 300; $i++) {
// 写入文件,大概要3000微妙
usleep(3000);
echo "写入文件{$i}\n";
}
}
function task2() {
for ($i = 0; $i <= 500; $i++) {
// 发送邮件给500名会员,大概3000微妙
usleep(3000);
echo "发送邮件{$i}\n";
}
}
function task3() {
for ($i = 0; $i <= 100; $i++) {
// 模拟插入100条数据,大概3000微妙
usleep(3000);
echo "插入数据{$i}\n";
}
}
task1();
task2();
task3();
```
测试:
```shell
time php tmp.php
结果:
real 0m2.824s
user 0m0.015s
sys 0m0.027s
```
在这个代码中,我们主要做了3件事:写入文件、发送邮件、以及插入数据。再看下面这段代码:
```php
<?php
function task1($i) {
// 使用$i标识,写入文件,大概要3000微妙
if ($i > 300) {
return false; // 超过300不用写了
}
echo "写入文件{$i}\n";
usleep(3000);
return true;
}
function task2($i) {
// 使用$i标识,发送邮件,大概要3000微妙
if ($i > 500) {
return false; // 超过500不用发送了
}
echo "发送邮件{$i}\n";
usleep(3000);
return true;
}
function task3($i) {
// 使用$i标识,插入数据,大概要3000微妙
if ($i > 100) {
return false; // 超过100不用插入
}
echo "插入数据{$i}\n";
usleep(3000);
return true;
}
$i = 0;
$task1Result = true;
$task2Result = true;
$task3Result = true;
while (true) {
$task1Result && $task1Result = task1($i);
$task2Result && $task2Result = task2($i);
$task3Result && $task3Result = task3($i);
if ($task1Result == false && $task2Result == false && $task3Result == false) {
break; // 全部任务完成,退出循环
}
$i++;
}
```
测试:
```shell
time php tmp.php
结果:
real 0m2.808s
user 0m0.023s
sys 0m0.018s
```
这段代码也是做了3件事,写入文件,发送邮件,以及插入数据,但是和上面的不同的是,这段代码将3件事交叉执行,每个任务执行完一次之后,切换到另一个任务,如此循环。
类似于这样的执行顺序,就是协程。
> 协程是指一种用代码实现任务交叉执行的逻辑,协程可以使得代码1中的3个函数交叉运行,在实现了协程的框架中,我们不需要通过代码2的方式实现交叉执行。直接可让代码1中的while(1),执行一次协程切换。
<br />
## 协程的实现
在 php 中,实现协程主要使用2种方式:
* yield 生成器实现(详细原理可查看:[协程](http://blog.huanghui.xyz/2019/12/24/Swoole/Swoole%E5%9F%BA%E7%A1%80/php-yield%E5%85%B3%E9%94%AE%E5%AD%97%E4%BB%A5%E5%8F%8A%E5%8D%8F%E7%A8%8B%E7%9A%84%E5%AE%9E%E7%8E%B0/))
* swoole 扩展实现
swoole 实现协程代码:
```php
<?php
function task1() {
for ($i = 0; $i <= 300; $i++) {
// 写入文件,大概3000微妙
usleep(3000);
echo "写入文件{$i}\n";
Co::sleep(0.001); // 挂起当前协程,0.001秒后恢复 // 相当于切换协程
}
}
function task2() {
for ($i = 0; $i <= 500; $i++) {
// 发送邮件给501名会员,大概3000微妙
usleep(3000);
echo "发送邮件{$i}\n";
Co::sleep(0.001); // 挂起当前协程,0.001秒后恢复 // 相当于切换协程
}
}
function task3() {
for ($i = 0; $i <= 100; $i++) {
// 模拟插入101条数据,大概3000微妙
usleep(3000);
echo "插入数据{$i}\n";
Co::sleep(0.001); // 挂起当前协程,0.001秒后恢复 // 相当于切换协程
}
}
$pid1 = go('task1'); // go函数是swoole的开启协程函数,用于开启一个协程
$pid2 = go('task2');
$pid3 = go('task3');
```
测试:
```shell
time php tmp.php
结果:
real 0m5.678s
user 0m0.029s
sys 0m0.031s
```
以上代码,即可实现切换函数。
> 为什么要用 sleep 挂起协程实现切换呢?因为 swoole 的协程是自动的,当协程内遇上 I/O 操作(mysql、redis)等时,swoole 的协程会自动切换,运行到下一个协程中(切换后,I/O 继续执行)直到下一个协程任务完成或者被切换(遇到 I/O),如此反复,直到所有协程任务完成,则任务完成。
<br />
## 协程与进程
由上面的 `协程执行顺序`中的代码2,我们很容易发现,协程其实只是运行在一个进程中的函数,只是这个函数会被切换到下一个执行,可以这么说:
> 协程只是一串运行在进程中的任务代码,只是这些任务代码可以交叉运行,注意,协程并不是多任务并行,属于多任务串行,每个进程在一个时间只执行了一个任务。
<br />
## 协程的作用域
由于协程就是进程中一串任务代码,所以它的全局变量、静态变量等变量都是共享的,包括了 php 的全局缓冲区。
所以,在开发之中,需要特别注意协程中的全局变量、静态变量,只要某一个协程内修改了,那将会影响到全部的协程,在使用 ob 缓冲区函数拦截的时候,也得考虑是否会被其他协程的输出给污染。
用 `协程执行顺序` 中的代码2解释,当 task1 给 $\_GET['name'] 赋值为1时,task2 读取 $\_GET['name'] 也会是1,task2 将 $\_GET['name'] 赋值为2时,task3 读取 $_GET['name'] 也会是2。
<br />
## 协程中的 I/O 连接
在协程中,要特别注意不能共用一个 I/O 连接,否则会造成数据异常。用 `协程执行顺序` 中的代码2解释,当 task1,task2 函数共用 mysql 连接,并都进行查询时,由于协程是交叉运行的,可能造成 task1 获取到 task1 + task2 查询出来的数据,也可能会丢失部分数据,被 task2 读取。
> 由于协程的交叉运行机制,各个协程的 I/O 连接都必须是独立的,所以我们需要在每个协程都创建一个连接,但由于 mysql、redis 的连接数有限,以及连接的开启关闭需要消耗大量资源,所以我们可以使用连接池方案实现共用连接(只要保证每个连接每次只有一个协程在使用即可)
- 引言
- Introduction
- 运行模式
- php-fpm
- php-cli
- 基础介绍
- 网络协议
- ip
- tcp
- tcp
- http
- webSocket
- udp
- port端口
- 会话管理
- cookie
- session
- api/token
- linux基础
- lnmp安装
- 命令
- 进程管理
- 扩展安装
- 端口监控
- 防火墙说明
- php7.0
- 部分新特性
- php回调/闭包
- 回调事件
- 闭包/匿名函数
- php多进程
- 多进程开启
- 进程通信
- 进程信号
- 僵尸进程
- 孤儿进程
- 守护进程
- 同步/异步
- 阻塞/非阻塞
- 协程
- Swoole
- 初始Swoole
- 运行机制
- 生命周期
- composer使用
- EasySwoole
- 设计理念
- 组件说明
- 运行过程
- demo
- 提问的艺术