协程是什么

进程与线程

  1. 进程是操作系统分配资源的最小单位,而线程是程序执行的最小单位
  2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见
  4. 调度和切换:线程上下文切换比进程上下文切换要快得多

协程

协程不是进程,也不是线程,它就是一个函数,一个特殊的函数——可以在某个地方挂起,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比,不是一个维度的概念。

一个线程内的多个协程虽然可以切换,但是这多个协程是串行执行的,只能在这一个线程内运行,没法利用CPU多核能力

多进程+协程 可以有效利用多核

协程的适用场景

  • 高并发服务,如秒杀系统、高性能 API 接口、RPC 服务器,使用协程模式,服务的容错率会大大增加,某些接口出现故障时,不会导致整个服务崩溃;
  • 爬虫,可实现非常强大的并发能力,即使是非常慢速的网络环境,也可以高效地利用带宽;
  • 即时通信服务,如 IM 聊天、游戏服务器、物联网、消息服务器等等,可以确保消息通信完全无阻塞,每个消息包均可即时地被处理。

协程引入的问题

协程再为我们带来便利的同时,也引入了一些新的问题:

  • 协程需要为每个并发保存栈内存并维护对应的虚拟机状态,如果程序并发很大可能会占用大量内存;
  • 协程调度会增加额外的一些 CPU 开销。

尽管如此,在处理高并发应用时,使用协程带来的优势还是远远高于 PHP 默认的同步阻塞机制。

swoole 中的协程

Swoole 可以为每一个请求创建对应的协程,根据 IO 的状态来合理的调度协程。

在 Swoole 4.x 中,协程(Coroutine)取代了异步回调,成为 Swoole 官方推荐的编程方式。

协程 vs 线程

Swoole 的协程在底层实现上是单线程的,因此同一时间只有一个协程在工作,协程的执行是串行的,这与线程不同,多个线程会被操作系统调度到多个 CPU 并行执行。

一个协程正在运行时,其他协程会停止工作。当前协程执行阻塞 IO 操作时会挂起,底层调度器会进入事件循环。当有 IO 完成事件时,底层调度器恢复事件对应的协程的执行。

在 Swoole 中对 CPU 多核的利用,仍然依赖于 Swoole 引擎的多进程机制。

协程 vs 生成器

一些框架中会使用 PHP 的生成器来实现半自动化的协程,但在实际使用中,开发者需要在涉及协程逻辑的函数调用前增加 yield 关键字,这带来了额外的学习和维护成本,非常容易犯错,此外 Yield/Generator 代码风格与传统的同步风格代码存在冲突,无法复用已有代码。

Swoole 协程是全自动化的协程,开发者无需添加任何关键字,底层自动实现协程的切换和调度,此外,Swoole 协程风格与传统的同步风格代码是一致的,因此可以复用已有代码。

使用时的注意事项

编程范式

  • 协程之间通讯不要使用全局变量或者引用外部变量到当前作用域,而要使用 Channel
  • 项目中如果有扩展 hook 了 zend_execute_ex 或者 zend_execute_internal 这两个函数,需要特别注意一下 C 栈,可以使用 co::set 重新设置 C 栈大小

扩展冲突

由于某些跟踪调试的 PHP 扩展大量使用了全局变量,可能会导致 Swoole 协程发生崩溃,请关闭这些相关扩展:

  • xdebug
  • phptrace
  • aop
  • molten
  • xhprof
  • phalcon(Swoole协程无法运行在 phalcon 框架中)

严重错误

由于多个协程是并发执行的,所以以下行为可能会导致协程出现严重错误:

  • 不能使用类静态变量/全局变量保存协程上下文内容,否则可能导致变量被污染,要使用 Context 管理上下文
  • 同一时间可能会有很多个请求在并行处理,多个协程共用一个客户端连接的话,就会导致不同协程之间发生数据错乱

基本使用示例

服务端

以 Swoole 自带的 TCP 服务器 Swoole\Server 实现为例

客户端

先运行服务端,再运行客户端,会看到服务端返回的信息Swoole: hello world


REFERENCE

分类: 后端

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注