为什么下面的代码不是顺序执行的呢? 实际执行过程:
运行此段代码, 系统启动一个新进程
遇到 go(), 当前进程中生成一个协程
协程中遇到 IO阻塞 (这里是 Co::sleep() 模拟出的 IO等待), 协程让出控制, 进入协程调度队列
进程继续向下执行, 输出 主作业
执行下一个协程, 输出 作业02
之前的协程准备就绪, 继续执行, 输出 作业01
go(function () {
//Co::sleep() 函数功能和 sleep() 差不多, 但是它模拟的是 IO等待
Co::sleep(1);
echo "作业01".PHP_EOL;
});
echo "主作业".PHP_EOL;
go(function () {
echo "作业02".PHP_EOL;
});
协程快在哪? 减少IO阻塞导致的性能损失
大家可能听到使用协程的最多的理由, 可能就是 协程快. 那看起来和平时写得差不多的代码, 为什么就要快一些呢? 一个常见的理由是, 可以创建很多个协程来执行任务, 所以快. 这种说法是对的, 不过还停留在表面.
首先, 一般的计算机任务分为 2 种:
CPU密集型, 比如加减乘除等科学计算
IO 密集型, 比如网络请求, 文件读写等
其次, 高性能相关的 2 个概念:
并行: 同一个时刻, 同一个 CPU 只能执行同一个任务, 要同时执行多个任务, 就需要有多个 CPU 才行
并发: 由于 CPU 切换任务非常快, 快到人类可以感知的极限, 就会有很多任务 同时执行 的错觉
了解了这些, 我们再来看协程, 协程适合的是 IO 密集型 应用, 因为协程在 IO阻塞 时会自动调度, 减少IO阻塞导致的时间损失.
普通写法, 会遇到 IO阻塞 导致的性能损失
单协程: 尽管 IO阻塞 引发了协程调度, 但当前只有一个协程, 调度之后还是执行当前协程
多协程: 真正发挥出了协程的优势, 遇到 IO阻塞 时发生调度, IO就绪时恢复运行
sleep() 可以看做是 CPU密集型任务, 不会引起协程的调度
Co::sleep() 模拟的是 IO密集型任务, 会引发协程的调度
这也是为什么, 协程适合 IO密集型 的应用.
go 中的协程, 使用的 MPG 模型:
M 指的是 Machine, 一个M直接关联了一个内核线程
P 指的是 processor, 代表了M所需的上下文环境, 也是处理用户级代码逻辑的处理器
G 指的是 Goroutine, 其实本质上也是一种轻量级的线程
而 swoole 中的协程调度使用 单进程模型, 所有协程都是在当前进程中进行调度, 单进程的好处也很明显 -- 简单 / 不用加锁 / 性能也高