协程(coroutine)可以理解为一个可以中途暂停保存当前执行状态信息并可以从此处恢复执行的函数,多个协程共用一个线程执行,适合执行需要“等待”的任务。
所以严格意义上,多个协程同一时刻也只有一个在真正的执行,因为线程是任务调度的基本单位。注意这里的执行指的是占用CPU计算,不包括等待阻塞等场景。
python中实现协程的标准库是asyncio,标准库的实现中大致可以由底层到顶层分为下面几个对象:
coroutine
:一个可以暂停和恢复的函数,它允许在执行过程中挂起自身,并在稍后的时间点恢复执行。协程通常通过 async def 定义,python 中当调用一个协程函数时,实际上返回的是一个协程对象,而不是立即执行该函数体中的代码。task
:事件循环中真正调度执行的对象,future的子类,封装了coroutine以及执行的上下文信息。future
:在某种意义上,是一种“未来要执行的任务”的封装,当一个任务被封装成future后,就可以交给事件循环执行。- 事件循环:异步程序的核心机制,负责调度和执行任务。它是一个无限循环,不断从任务队列中获取任务并执行,直到所有任务完成。
coroutine/task/future都是可await获取执行结果的,但await coroutine并不会创建task,会挂起同步等待await的协程执行完再回到当前协程继续执行,而await task/future时,会异步等待task完成。关键点还是在于事件循环中调度执行的是task而不是coroutine。
下面是一些场景的测试:
1)直接await coroutine时,阻塞执行,两个协程各自sleep 1s,共用时2s+
#!/usr/bin/env python3
# -*-coding:utf-8 -*-
import os
import asyncio
import threading
from datetime import datetime
async def fun(num1, num2):
await asyncio.sleep(1)
print(f'pid:{os.getpid()} | tid:{threading.current_thread().name} | num1:{num1} | num2:{num2} | time:{datetime.now()}')
return num2 - num1
async def main():
start_time = datetime.now()
print(f'main start: {start_time}')
res1 = await fun(1, 2)
res2 = await fun(10, 20)
end_time = datetime.now()
print(f'main end: {end_time}')
print(f"res1:{res1} | res2:{res2} | cost: {(end_time - start_time).microseconds}ms")
if __name__ == '__main__':
asyncio.run(main())
2)await task时,非阻塞执行,两个task各自sleep 1s,共用时1s+
#!/usr/bin/env python3
# -*-coding:utf-8 -*-
import os
import asyncio
import threading
from datetime import datetime
async def fun(num1, num2):
await asyncio.sleep(1)
print(f'pid:{os.getpid()} | tid:{threading.current_thread().name} | num1:{num1} | num2:{num2} | time:{datetime.now()}')
return num2 - num1
async def main():
start_time = datetime.now()
print(f'main start: {start_time}')
task1 = asyncio.create_task(fun(1, 2))
task2 = asyncio.create_task(fun(10, 20))
res1 = await task1
res2 = await task2
end_time = datetime.now()
print(f'main end: {end_time}')
print(f"res1:{res1} | res2:{res2} | cost: {(end_time - start_time).microseconds}ms")
if __name__ == '__main__':
asyncio.run(main())
3)await future,非阻塞执行,三个协程各自sleep 1s,共用时1s+
gather中传递协程对象时会隐式转为task。
#!/usr/bin/env python3
# -*-coding:utf-8 -*-
import os
import asyncio
import threading
from datetime import datetime
async def fun(num1, num2):
await asyncio.sleep(1)
print(f'pid:{os.getpid()} | tid:{threading.current_thread().name} | num1:{num1} | num2:{num2} | time:{datetime.now()}')
return num2 - num1
async def main():
start_time = datetime.now()
print(f'main start: {start_time}')
futures = asyncio.gather(fun(1, 2), fun(10, 20))
res = await asyncio.gather(futures, fun(100, 200))
end_time = datetime.now()
print(f'main end: {end_time}')
print(f"res:{res} | cost: {(end_time - start_time).microseconds}ms")
if __name__ == '__main__':
asyncio.run(main())