python 异步编程
1:协程
协程不是操作系统提供的,是一种用户状态内的上下文切换技术,简言而之,其实就是通过一个线程实现代码块相互切换执行。
def func1(): print(1) ... print(2) def func2(): print(3) ... print(4) func1() func2()
- greenlet,是一个第三方实现协程的代码,(Gevent协程就是通过greenlet实现的)
- yield,生成器,借助生成器的特点亦可以实现协程代码
- asyncio,在python3.4 种引入的模块,用于编写协程代码
- async & awiat,在python3.5种引入的两个关键字,结合asyncio模块可以方便白那些协程代码
2.1: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中也可以传递参数用于在切换执行时相互传递值。
2.2 yield 基于python 的生成器yield 和 yield from 关键字实现协程代码
def func1(): yield 1 yield from func2() yield 2 def func2(): yield 3 yield 4 f1 = func1() for item in f1: print(item) # 1,3,4,2
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))
2.4 async & awit
async & awit 关键字在Python3.5版本中正式引入,基于他编写的协程代码其实就是 上一示例 的加强版,让代码可以更加简便。
Python3.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))
3:协程的意义
协程就是可以通过一个线程在多个上下文种进行来回切换执行。但是协程协程来回切换执行的意义何在呢?
计算型的操作,利用协程来回切换执行,没有任何意义,来回切换并保存状态 反倒会降低性能。
IO型的操作,利用协程在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提供性能,从而实现异步编程(不等待任务结束就可以去执行其他代码)
4:异步编程
基于async
& await
关键字的协程可以实现异步编程,这也是目前python异步相关的主流技术。
# 伪代码 任务列表 = [ 任务1, 任务2, 任务3,... ] while True: 可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回 for 就绪任务 in 已准备就绪的任务列表: 执行已就绪的任务 for 已完成的任务 in 已完成的任务列表: 在任务列表中移除 已完成的任务 如果 任务列表 中的任务都已完成,则终止循环
在编写程序时候可以通过如下代码来获取和创建事件循环。
import asyncio loop = asyncio.get_event_loop()
4.2 协程和异步编程
协程函数,定义形式为
协程对象,调用 协程函数
# 定义一个协程函数 async def func(): pass # 调用协程函数,返回一个协程对象 result = func()
4.2.1 基本使用
import asyncio async def func(): print("协程内部代码") # 调用协程函数,返回一个协程对象。 result = func() # 方式一 # loop = asyncio.get_event_loop() # 创建一个事件循环 # loop.run_until_complete(result) # 将协程当做任务提交到事件循环的任务列表中,协程执行完成之后终止。 # 方式二 # 本质上方式一是一样的,内部先 创建事件循环 然后执行 run_until_complete,一个简便的写法。 # asyncio.run 函数在 Python 3.7 中加入 asyncio 模块, asyncio.run(result)
4.2.2 await
import asyncio async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。 # 当前协程挂起时,事件循环可以去执行其他协程(任务)。 response = await asyncio.sleep(2) print("IO请求结束,结果为:", response) result = func() asyncio.run(result)
例二:
import asyncio async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值' async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 response = await others() print("IO请求结束,结果为:", response) asyncio.run( func() )
例三:
import asyncio async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值' async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 response1 = await others() print("IO请求结束,结果为:", response1) response2 = await others() print("IO请求结束,结果为:", response2) asyncio.run( func() )
在程序想要创建多个任务对象,需要使用Task对象来实现。
When a coroutine is wrapped into a Task with functions like
Tasks用于并发调度协程,通过asyncio.create_task(协程对象)
的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。除了使用 asyncio.create_task()
函数以外,还可以用低层级的 loop.create_task()
或 ensure_future()
本质上是将协程对象封装成task对象,并将协程立即加入事件循环,同时追踪协程的状态。
注意:asyncio.create_task()
函数在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future()
函数
import asyncio async def func(): print(1) await asyncio.sleep(2) print(2) return "返回值" async def main(): print("main开始") # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task1 = asyncio.create_task(func()) # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task2 = asyncio.create_task(func()) print("main结束") # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待相对应的协程全都执行完毕并获取结果 ret1 = await task1 ret2 = await task2 print(ret1, ret2) asyncio.run(main())
import asyncio async def func(): print(1) await asyncio.sleep(2) print(2) return "返回值" async def main(): print("main开始") # 创建协程,将协程封装到Task对象中并添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 # 在调用 task_list = [ asyncio.create_task(func(), name="n1"), asyncio.create_task(func(), name="n2") ] print("main结束") # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。 done, pending = await asyncio.wait(task_list, timeout=None) print(done, pending) asyncio.run(main())
import asyncio async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 response = await asyncio.sleep(2) print("IO请求结束,结果为:", response) coroutine_list = [func(), func()] # 错误:coroutine_list = [ asyncio.create_task(func()), asyncio.create_task(func()) ] # 此处不能直接 asyncio.create_task,因为将Task立即加入到事件循环的任务列表, # 但此时事件循环还未创建,所以会报错。 # 使用asyncio.wait将列表封装为一个协程,并调用asyncio.run实现执行两个协程 # asyncio.wait内部会对列表中的每个协程执行ensure_future,封装为Ta