🙈 什么是协程(Coroutine)? - 图1

先来看一下 Python 对于协程的解释:

Coroutines are a more generalized form of subroutines(子程序). Subroutines are entered at one point and exited at another point. Coroutines can be entered, exited, and resumed at many different points. They can be implemented with the [async def](https://docs.python.org/3/reference/compound_stmts.html#async-def) statement. See also PEP 492.

熟悉 Python 的同学,相信对于线程进程并不陌生,毕竟这是进阶高手的必经之路,我们下面要讲的协程与线程是紧密相关的,与进程并没有太大的关系。很多书上也将协程称作微线程。这里先说一下我对于协程概念的理解,协程就是对单个线程进行最优化处理。

1.1 简单示例——线程和协程

下面用一个最简单的生活例子来描述一下线程与协程的差异,现在我们有一个主题是做家务,包含以下事项:

  • 烧开水:电水壶灌满水需要 2 分钟,水烧开需要 10 分钟,将水倒进热水瓶中需要 3 分钟,共计 15 分钟
  • 洗衣服:收拾需要洗的衣服需要 5 分钟,放进洗衣机之后衣服洗好需要 15 分钟,晾衣服需要 5 分钟,共计 25 分钟
  • 拖地:拖地需要 10 分钟

我们的目标是完成上述 3 项任务,所需最短时间是多少
image.png

简单解释一下上面这个图:

  • 最左边是脑子傻傻的我,只知道一件事情做完再做另外一件事情,这样完成所有事情需要花费 50 分钟
  • 中间是有钱人则是做法,再雇 2 个人帮我工作,这样一共只用花 25 分钟
  • 最右边则是机智的我,合理安排要做的事情,最后也是只花了 25 分钟

看了上述示例,可以简单概括一下协程的定义:协程的作用就是单个线程下对于资源优化调用,完成不同子程序之间的切换

当然比较官方的解释:协程就是一个可以暂停将执行权让给其他协程或等待对象的函数,等其执行完成后,并可以多次的进行这样的暂停与继续。

与多线程相比,协程具备以下优势:

  • 在实现多任务时,线程切换从系统层面远不止保存和恢复 CPU 上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存 **Cache** 等数据,操作系统还会帮你做这些数据的恢复操作。 但是协程的切换只是单纯的操作 **CPU** 的上下文,所以一秒钟切换个上百万次系统都抗的住。
  • 不需要多线程的锁机制

想要充分利用多核 CPU,我们应该尝试多进程+协程

:::tips 🔔 对 Python 中线程和进程还不是很清楚的话,可以参考下我整理的另一篇文章——多线程与多进程。 :::

1.2 同步与异步

如果你搜索过协程的相关内容,会发现很多博客都会提到同步异步的概念,包括阻塞与非阻塞,这几个概念都是围绕 IO 展开的。为了提高对 IO 的处理效率,通常我们会采用多线程+多进程的并发方式,也就是所谓的专人做专事,但这个也会带来一个问题:如果进程与线程过多,也会导致 CPU 开销增大,协程正是为了高效利用 CPU,且线程越多,协程的优势越明显。

此外,在关于同步与异步的解释,我们经常在 Web 开发中看到同步和异步请求,也是相关的概念:

  • 同步请求:你提交的一个 request 请求,没有获得 repsonse 响应的话,页面处于一直请求的状态,不会跳转到另一个页面或刷新
  • 异步请求:你提交的一个请求,但是数据并没有完全获取完,页面会先跳转到另一个页面,然后等数据完全获取后,相应的报表数据才会更新。

现如今在 Web 开发中异步请求对于用户才会更友好,如果让用户点击一个请求需要等待 5 分钟,那显然是不合理的,所以异步请求也是 Web 开发下进阶必经之路。只不过不同于 Python 的后端的异步 IO 操作,异步请求是放在前端开发的 JS 框架中的。

1.3 发展历程

  1. 最初的生成器 yield / send
  2. 引入 @asyncio.coroutineyield from
  3. Python 3.5 之后引入 asyncawait 关键字

    1.4 应用场景

    协程主要在以下场景下使用较多:
  • 爬虫