python - 协程的简单实现

61 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情

背景

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术

简而言之,其实就是通过一个线程实现代码块相互切换执行

def func1():
	print(1)
    ...
	print(2)
	
def func2():
	print(3)
    ...
	print(4)

func1()
func2()

上述代码是普通的函数定义和执行,按流程分别执行两个函数中的代码,并先后会输出:1、2、3、4

但如果介入协程技术那么就可以实现函数见代码切换执行,最终输入:1、3、2、4

协程的实现

在Python中有多种方式可以实现协程

  • greenlet,是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现)
  • yield,生成器,借助生成器的特点也可以实现协程代码
  • asyncio,在Python3.4中引入的模块用于编写协程代码
  • async & awiat,在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码

1.1 greenlet

greentlet 是一个第三方模块,需要提前安装 pip3 install greenlet才能使用

from greenlet import greenlet


def func1():
    print(1)        # 第1步:输出 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 函数

注意:switch 中也可以传递参数用于在切换执行时相互传递值

1.2 yield

基于 Python 的生成器的 yield 和yield form关键字实现协程代码

def func1():
    yield 1
    yield from func2()
    yield 2


def func2():
    yield 3
    yield 4


f1 = func1()
for item in f1:
    print(item)

注意:yield form 关键字是在 Python 3.3 中引入的

1.3 asyncio

在 Python 3.4 之前官方未提供协程的类库,一般大家都是使用 greenlet 等其他来实现

在 Python 3.4 发布后官方正式支持协程,即:asyncio模块

import asyncio

@asyncio.coroutine
def func1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)


@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2) # 遇到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))

注意:基于asyncio模块实现的协程比之前的要更厉害,因为他的内部还集成了遇到IO耗时操作自动切花的功能。

1.4 async & awit

  • async & awit 关键字在 Python 3.5 版本中正式引入,基于他编写的协程代码其实就是上一示例的加强版,让代码可以更加简便
  • Python 3.8 之后 @asyncio.coroutine 装饰器就会被移除,推荐使用 async & awit 关键字实现协程代码
import asyncio


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


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


tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

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

1.5 小结

关于协程有多种实现方式,目前主流使用是 Python 官方推荐的 asyncio 模块和 async&await 关键字的方式,例如:在tonado、sanic、fastapi、django3 中均已支持