python 协程详解
Python中的协程经历了很长的一段发展历程。其大概经历了如下三个阶段
- 最初的生成器变形
yield/send
- 引入
@asyncio.coroutine
和yield from
- 在最近的
Python3.5
版本中引入async/await
关键字
协程是什么
协程,又称微线程,纤程。英文名Coroutine,是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。
协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。
因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
寄存器上下文和栈保存到其他地方
线程和进程的上下文切换是由内核管理的。协程则是在用户态下运行,它不依赖于内核的调度,而是由用户程序自行管理。
因此,协程的上下文保存和恢复通常是由程序库(如 Python 的 asyncio 或 Go 的 goroutine 库)来处理的。
具体来说,协程的上下文和栈保存位置可以是
- 程序的堆内存:协程上下文和栈信息通常会保存在程序的堆内存中。每个协程会有一块专门分配的内存区域来保存它的状态,包括寄存器内容和栈帧。
- 数据结构:一些程序库会使用特定的数据结构(如链表或数组)来保存多个协程的上下文信息。当协程切换时,当前协程的上下文信息会被存储在这些数据结构中,下一个要运行的协程的上下文信息会被取出并恢复。
从yield说起
有一个描述很好的说明了yield
的作用:保留计算现场。
生成器(Generators)是Python中实现协程的一种方式,它通过内置的yield
关键字来暂停和恢复执行。当函数遇到yield
时,
会暂停执行并返回一个值,下次调用时会从上次暂停的地方继续执行。yield
实际上是一个特殊的return语句,
它会保存当前的状态(包括局部变量和执行上下文),当再次调用时,这些状态会被恢复。
1 | def coroutine_example(): |
协程与多线程/多进程的区别
- 多线程:线程是操作系统层面的并行执行单位,线程间通信需要锁等同步机制,上下文切换开销大,适合CPU密集型任务。
- 多进程:进程是独立的执行环境,拥有自己的内存空间,适合I/O密集型任务,但创建和销毁进程开销大。
- 协程:协程在单线程中通过控制流切换实现并发,没有线程切换开销,但资源占用相对较少,适合I/O等待任务。
协程的生命周期与状态转换
- 创建:函数定义为生成器,使用yield关键字。
- 启动:通过调用生成器实例的next()或send()方法开始执行,直到遇到yield。
- 暂停:遇到yield时,函数暂停,保存当前状态。
- 恢复:通过send()方法传入值,函数从上次暂停的地方继续执行。
- 结束:当没有更多yield可执行(StopIteration异常。此异常会被for循环或其他方式自动捕获),或遇到return语句时,协程结束。
使用asyncio库
asyncio
是 Python 中用于编写异步代码的标准库,它提供了一组工具和API来管理和调度协程。通过asyncio
,可以轻松创建、执行和管理异步任务。
1 | import asyncio |
异步函数与async/await
async
关键字用于定义异步函数,await
关键字用于暂停异步函数的执行,等待另一个异步任务完成。
1 | import asyncio |
协程的调度与调度器
asyncio
提供了事件循环(Event Loop)来调度协程的执行。事件循环负责管理和调度所有的协程任务,确保它们按照正确的顺序执行。
1 | import asyncio |