什么是协程
- 协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。
- 协程不是进程或线程,其执行过程类似于子例程,或者说不带返回值的函数调用
- 一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因此以下对比协程和线程。而多个线程相对独立,有自己上下文,切换受系统的控制
而协程也相对独立,有自己的上下问,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制
-
多进程执行流程
-
代码执行流程 →
-
-
正常执行图 → 任务1 → 任务2 → 任务3 → 完成执行,进程退出。
-
-
→ 任务1 \
-
多进程执行执行图 → 任务2 → 完成执行,进程退出
-
→ 任务3 /
-
协程执行流程
-
代码执行流程 →
-
正常执行图 → 任务1 → 任务2 → 任务3 → 完成执行,进程退出
-
-
-
协程执行图 任务1 → 协程切换
-
↓
-
任务2 → 协程切换
-
↓
-
任务3 → 协程切换
-
↓
-
任务1 → 协程切换
-
↓
-
任务2 → 协程切换
-
↓
-
任务3 → 协程切换
-
\ /
-
\ /
-
\ /
-
\ /
-
\ /
-
\ /
-
↓
-
完成执行,进程退出
协程执行顺序
- 原生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();
-
?>
- 下面这段代码也是做了3件事,写入文件、发送邮件、及插入数据。但是和上面的不同的是,这段代码将这3件事交叉执行,每个任务执行完一次之后,切换到另一个任务,如此循环。类似于这样的执行顺序,就是协程。
-
<?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++;
-
}
-
?>
协程是一种用代码实现任务交叉执行的逻辑,协程可以使得代码1中的3个函数交叉运行,在实现了协程的一种框架中,我们不需要通过代码2的方法实现任务交叉运行。直接可让代码1中的while(1),执行一次后切换。
协程的实现
在php中,实现协程主要采用两种方式
- yield生成器实现(详细原理可查看http://www.php20.cn/article/148)
- swoole扩展实现
- swoole实现协程代码
-
<?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++){
-
//发送邮件给500名会员,大概3000微秒
-
usleep(
3000);
-
echo
"发送邮件{$i}\n";
-
Co::sleep(
0.001);
//挂起当前协程,0.001秒后恢复//相当于切换协程
-
}
-
}
-
function task3(){
-
for ($i=
0;$i<=
100;$i++){
-
//模拟插入100条数据,大概3000微秒
-
usleep(
3000);
-
echo
"插入数据{$i}\n";
-
Co::sleep(
0.001);
//挂起当前协程,0.001秒后恢复//相当于切换协程
-
}
-
}
-
$pid1 = go(
'task1');
//go函数是swoole的开启协程函数,用于开启一个协程
-
$pid2 = go(
'task2');
-
$pid3 = go(
'task3');
-
?>
以上代码,即可实现切换函数。
协程与进程
由上面的协程执行顺序中的代码2,我们很容易发现,协程其实只是运行在一个进程中的函数,只是这个函数会被切换到下一个执行,可以这么说:
协程只是一串运行在进程中的任务代码,只是这些任务代码可以交叉运行。 注意,协程并不是多任务并行,属于多任务串行,每个进程在一个时间只执行了一个任务。
协程注意事项
变量使用
- 在协程中,需要特别注意,不要使用$_GET,$_POST,$GLOBAL等超全局变量,尤其是需要修改变量值并读取时,将造成协程间变量数据错乱
- 协程中访问外部变量必须使用use关键字,或者传形参方式,不能引用变量。
- 如果要做多协程之间的通信,可以使用channel方式通信。
扩展冲突
- 与xdebug、xhprof、balckfire等zend扩展不兼容,例如不能使用xhprof对协程Server进行性能分析采样
转载:https://blog.csdn.net/jartins/article/details/113339218
查看评论