协程

五十岚2021年7月3日
大约 7 分钟

协程 Coroutine

**协程**不是计算机提供的,而是程序员创造

一、协程概念:

1.1 为什么要有协程?

痛点:需求:结果:

产生协程,多进程和多线程是内核级别的程序,而协程是函数级别的程序,可以通过程序员自己控制,因此:

1.2 什么是协程

协程,又称微线程|纤程。英文:Coroutine 可揉挺


一句话说明:协程是一种 用户态轻量级 线程。通过一个线程,实现代码块相互切换执行,实现麻烦但效率极佳。


以前在系统里开线程,避免 IO,实现并发。 现在在一个线程里切换阻塞代码,避免 IO,实现并发

1.3 协程的优缺点

协程の好处

协程の缺点

二、协程进化史:

实现前先给协程一个标准定义,即符合什么条件就能称之为协程

1. 必须在只有一个`单线程`里实现并发

2. 修改共享数据不需加锁

3. 用户程序里自己保存多个控制流的上下文栈

4. 一个协程遇到 IO 操作自动切换到其它协程

实现方式有以下几种:

  • 生成器 yield 关键字
  • greenlet/gevent 早期模块
  • yield from 实现
  • asyncio装饰器py3.4
  • async|await 关键字py3.5[主流实现]

2.1 yield 生成器实现

因为 yield 可以实现中断功能,所以起初,协程是用生成器来实现的,此时不是 线程级CPU 的切换,而是 执行顺序 的切换,但原理依旧

def func1():
    yield 1
    yield from func2()	# 切换到func2 执行,并保留上下文
    yield 2


def func2():
    yield 3
    yield 4


f1 = func1()

for item in f1:		# 返回了生成器
    print(item)

### 输出结果:
# 1
# 3
# 4
# 2
**注**: 此时的生成器主要用来解决占存大量数据问题,并没有实现遇到IO 阻塞自动切换

2.2 greenlet 实现协程

是一个用 c 实现的 协程模块,相比与python自带的yield,它能在任意函数之间随意切换,而不需把这个函数先声明为 generator|生成器

安装 greenlet

pip install greenlet
from greenlet import greenlet


def func1():
    print(1)            # 第2步:打印1
    gr2.switch()        # 第3步:切换到 func2 函数
    print(2)            # 第6步:打印2
    gr2.switch()        # 第7步:切换到 func2 函数,从上一次位置继续


def func2():
    print(3)            # 第4步:打印3
    gr1.switch()        # 第5步:切换到 func1 函数,从上一次位置继续
    print(4)            # 第8步:打印4


gr1 = greenlet(func1)
gr2 = greenlet(func2)

gr1.switch()            # 第1步:执行 func1 函数

## 输出结果:
# 1
# 3
# 2
# 4

与生成器相同,此时未实现遇见 IO 阻塞 自动切换,这在 gevent 中实现了sleep自动切换

2.3 yield from 和装饰器实现

yield from@asyncio.coroutine 是官方python 3.4之后专门提供用于实现异步I/O的模块

import asyncio


@asyncio.coroutine	# 装饰一下,变为协程函数
def func1():
    print(1)
    yield from asyncio.sleep(1)	# 当遇到IO操作时,会自动化切换到tasks中的其他任务执行
    print(2)


@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(1)	# 当遇到IO操作时,会自动化切换到tasks中的其他任务执行
    print(4)


tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]	# 打包任务

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

### 输出结果
# 1
# 3
# 2
# 4

以上为基于生成器的协程,已弃用 并计划在 Python 3.10 中移除。

2.4 async/await 实现

把上文的 装饰器 替换为 async , yield from 替换为 await 即可

async def func1():
    print(1)
    await asyncio.sleep(1)
    print(2)


async def func2():
    print(3)
    await asyncio.sleep(1)
    print(4)

具体见异步 I/O

2.5 协程本质

[本质]
协程是栈结构实现的,是将上下文出入栈的过程,`生成器` 占内存少也是因为只返回了栈帧,见[底层实现](/back_end/python/base/异步编程/底层实现#22-python上下文源码)