因为Python有GIL全局线程锁,所以在Python中对于IO密集型任务可采用多并发的异步实现方式。Python 3 中的协程是异步编程的一种形式,它允许在代码中创建可暂停的函数,以便其他任务可以运行,而不会阻塞程序的执行。关键字async和await 是 Python 3.5 之后引入的语法,用于编写协程。
async def 用于定义协程函数;await可以获取到可等待对象的返回值,当事件循环遇到await后会调度其他任务。
可等待对象包括:
- 协程对象:协程函数生成的对象;
- Task对象:通过asyncio.create_task(coroutine)(3.7开始有)创建,用于将任务添加到事件轮询(循环)中。
- Future对象。
在Python 3.7以前,使用协程的方式如下:
import asyncio
# 定义协程函数
async def work_proc(n):
for i in range(5):
print(f"我是协程任务{n},运行到第{i}步.")
await asyncio.sleep(1) # sleep参数单位为秒
work_obj = work_proc(1) # 得到协程对象,此时没有调度执行。协程函数不能直接调用
# 将协程函数转为协程对象,并放到列表中
tasks = [work_proc(1), work_proc(2)]
# 使用asyncio创建事件循环对象,用于并发调度多个协程任务
loop = asyncio.get_event_loop()
# 使用事件循环对象调度多个协程任务,列表中的多个协程任务并发异步执行
loop.run_until_complete(tasks)
Python 3.7及以上版本使用协程的方法:
import asyncio
# 定义协程函数
async def work_ret(n):
print(f"我是协程任务{n},开始执行...")
await asyncio.sleep(n)
print(f"我是协程任务{n},执行完毕.")
return f"这是我的{n}协程任务"
# 协程异步主函数
async def main_async():
print('协程任务准备调度...')
task1 = asyncio.create_task(work_ret(5))
task2 = asyncio.create_task(work_ret(2))
# 任务单独提交方式
res1 = await task1 # 继续调用后面的await代码
print('协程任务1已经提交执行...') # 此句会被阻塞,等待上句执行完毕立即执行,不受下个await影响
res2 = await task2 # 不受上一个await阻塞,上一个await提交后,立即提交该任务
print('协程任务2已经提交执行...') # 此句会被阻塞,等待上面2个await执行完毕执行
print(f"协程任务{1}完成,返回结果{res1}")
print(f"协程任务{2}完成,返回结果{res2}")
asyncio.run(main_async())
多任务并发的情况下,可采用创建任务循环列表的使用如下:
async def main_async():
tasks = [asyncio.create_task(work_ret(n)) for n in range(9)]
# 1.wait方式并发调度,获取到的返回值是乱序的
done, pending = await asyncio.wait(tasks) # tasks列表中所有任务并发,此处阻塞,done为任务的返回值,pending为状态
for item in done:
res = item.result() # res为每个任务的返回值
print(res)
# 2.gather方式并发调度,可收集所有任务的返回值,且返回值是有顺序的,对应任务列表的顺序
res = await asyncio.gather(*tasks) # tasks列表中所有任务并发,此处阻塞,res为返回结果组成的列表
print(res)
asyncio.run(main_async())