[知乎回答] 协程如何做到切换到另一个函数还能完成原函数的IO?

2021-11-18

知乎问题

协程的一个典型应用是:单线程上开启多协程去执行某个既包含cpu运算、又包含io操作的函数。在开始执行io操作之后切换到另一个函数上执行cpu运算,在上一个函数的io操作结束后再切换回来。

不太懂程序语言如何设计可以达到这一点?

能否以c/c++/java/python举例来说明?


1. 原理

原理:协程上下文切换 + epoll 事件管理。

协程上下文:寄存器 + 内存信息 + 当前运行源码的下一条源码地址。


协程切换主要有两个操作: yield(切出),resume (唤醒切入):

yield:保存当前(协程)程序 A 的上下文,加载其它(协程)程序的上下文。

resume:保存当前(协程)程序 A 运行的上下文,加载指定要唤醒(协程)程序 B 的上下文(之前被 yield 出来时保存的)。


2. 切换时机

一般是 IO 阻塞操作才会发生切换,假如单进程,单线程,阻塞执行一条 sql 命令,执行了 100 ms。在这个期间,因为是阻塞操作,进程不能干任何事,只能干等。


3. 协程 IO 工作流程

  1. 将阻塞操作改变为非阻塞,sql 命令往数据库发送,其实就是往一个 fd 上写数据。
  2. 数据发出去后非阻塞操作马上返回。然后将这个 fd 挂在 epoll 上(当发出去的命令返回或者超时,epoll_wait 会返回通知的)。
  3. 保存当前(协程)程序运行时的上下文,这样就相当于 yield 切走了当前(协程)程序 A。
  4. 然后加载其它等待执行的(协程)程序 B 的上下文,返回上一次 B 执行源码的下一条源码地址去运行,这样 A 在等待 sql 结果返回的过程中,被唤醒的 B 仍然能继续工作,B 如果遇到阻塞 IO 也同样执行上述步骤。
  5. sql 命令返回结果,epoll_wait 收到通知返回,resume 唤醒 fd 对应的 A(协程)程序。

4. 参考