Python中的协程经历了很长的一段发展历程。其大概经历了如下三个阶段

  1. 最初的生成器变形yield/send
  2. 引入@asyncio.coroutineyield from
  3. 在最近的Python3.5版本中引入async/await关键字

协程是什么

协程,又称微线程,纤程。英文名Coroutine,是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。
协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。
因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

寄存器上下文和栈保存到其他地方

线程和进程的上下文切换是由内核管理的。协程则是在用户态下运行,它不依赖于内核的调度,而是由用户程序自行管理。
因此,协程的上下文保存和恢复通常是由程序库(如 Python 的 asyncio 或 Go 的 goroutine 库)来处理的。

具体来说,协程的上下文和栈保存位置可以是

  • 程序的堆内存:协程上下文和栈信息通常会保存在程序的堆内存中。每个协程会有一块专门分配的内存区域来保存它的状态,包括寄存器内容和栈帧。
  • 数据结构:一些程序库会使用特定的数据结构(如链表或数组)来保存多个协程的上下文信息。当协程切换时,当前协程的上下文信息会被存储在这些数据结构中,下一个要运行的协程的上下文信息会被取出并恢复。

从yield说起

有一个描述很好的说明了yield的作用:保留计算现场。
生成器(Generators)是Python中实现协程的一种方式,它通过内置的yield关键字来暂停和恢复执行。当函数遇到yield时,
会暂停执行并返回一个值,下次调用时会从上次暂停的地方继续执行。yield 实际上是一个特殊的return语句,
它会保存当前的状态(包括局部变量和执行上下文),当再次调用时,这些状态会被恢复。

1
2
3
4
5
6
7
8
9
10
def coroutine_example():
value = yield 0
print(f'Received value: {value}')
value = yield 1
print(f'Received value: {value}')


c = coroutine_example()
next(c) # 输出 'Received value: 0'
print(c.send(2)) # 输出 'Received value: 1'

协程与多线程/多进程的区别

  • 多线程:线程是操作系统层面的并行执行单位,线程间通信需要锁等同步机制,上下文切换开销大,适合CPU密集型任务。
  • 多进程:进程是独立的执行环境,拥有自己的内存空间,适合I/O密集型任务,但创建和销毁进程开销大。
  • 协程:协程在单线程中通过控制流切换实现并发,没有线程切换开销,但资源占用相对较少,适合I/O等待任务。

协程的生命周期与状态转换

  • 创建:函数定义为生成器,使用yield关键字。
  • 启动:通过调用生成器实例的next()或send()方法开始执行,直到遇到yield。
  • 暂停:遇到yield时,函数暂停,保存当前状态。
  • 恢复:通过send()方法传入值,函数从上次暂停的地方继续执行。
  • 结束:当没有更多yield可执行(StopIteration异常。此异常会被for循环或其他方式自动捕获),或遇到return语句时,协程结束。

使用asyncio库

asyncio是 Python 中用于编写异步代码的标准库,它提供了一组工具和API来管理和调度协程。通过asyncio,可以轻松创建、执行和管理异步任务。

1
2
3
4
5
6
7
8
9
import asyncio


async def async_function():
await asyncio.sleep(1)
print("Async function executed")


asyncio.run(async_function())

异步函数与async/await

async关键字用于定义异步函数,await关键字用于暂停异步函数的执行,等待另一个异步任务完成。

1
2
3
4
5
6
7
8
9
import asyncio


async def async_function():
await asyncio.sleep(1)
print("Async function executed")


asyncio.run(async_function())

协程的调度与调度器

asyncio提供了事件循环(Event Loop)来调度协程的执行。事件循环负责管理和调度所有的协程任务,确保它们按照正确的顺序执行。

1
2
3
4
5
6
7
8
9
10
11
12
import asyncio


async def task():
print("Task executed")


async def main():
await asyncio.gather(task(), task())


asyncio.run(main())