Python中的协程与asyncio原理
Posted 小杰666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python中的协程与asyncio原理相关的知识,希望对你有一定的参考价值。
Python协程与asyncio原理
直接看Python代码,下面有详细的注释:
# 研究asyncio与协程的原理,python版本3.8
# 以下仅从代码执行与调试过程来理解协程,并不一定与协程的真正实现一致
# Python用保存函数的栈帧来恢复暂停点的继续执行,从而实现协程
import asyncio
import datetime
async def hi(num, sec):
print(f'enter hi(), #num @datetime.datetime.now()')
# 遇到await会挂起,执行后面的协程,等asyncio.sleep协程执行完,
# 通知事件循环从挂起状态恢复,并从此处继续往下执行
await asyncio.sleep(sec)
print(f'exit hi(), #num @datetime.datetime.now()')
# 注意,返回(即显式的return或隐式返回)表示函数执行完了,函数退出了,
# 而挂起表示函数执行过程中暂停了,下次可能还会回来,在暂停处恢复执行
return f'#num, sec'
async def main():
print(f'======== main() begin at datetime.datetime.now()')
# 如果直接执行下面三行,而不创建task任务,将是异步阻塞,hi协程不会有并发效果
# await hi(1, 2)
# await hi(2, 2)
# await hi(3, 2)
tasks = []
for i in range(1, 5):
t = asyncio.create_task(hi(i, i))
tasks.append(t)
# 通过 asyncio.run(main()) 把事件循环跑起来,并在里面执行main协程:
# 在main协程里创建task任务后,不管接下来的代码有没有await出现,任务列表里的所有协程一定会同时执行,
# 🚩同时执行协程,其实是按顺序把hi协程执行起来,遇到await立刻挂起,接着执行下个协程,
# 🚩注意,此时事件循环不会理会hi协程await后的sleep执行完成,并去恢复hi的挂起状态,
# 🚩而是直到任务列表里的所有协程都跑起来了,才处理已经执行完的sleep,并恢复对应的hi协程。
# 🚩也就是说,把任务列表里的所有协程跑起来的过程是同步的。
# 因为任务列表里的第1个hi协程,sleep时间最短,是1秒,所以它先执行完成,
# 事件循环会先恢复第1个hi协程的继续执行,以此类推。main协程里执行任务列表里的hi协程,分两种情况:
# ① 如果没有出现await语句,main协程退出(return或引发异常)后,开始并发(同上🚩处所述)执行hi协程
# ② 如果有await出现,main协程创建完任务后,在await的地方挂起,去执行hi协程(同上🚩处所述)
# 上面两种情况的区别是:
# 第①种,没有await,当main也执行结束,挂起的所有hi协程中,没有任何一个协程会被事件循环恢复执行,
# 因此只输出 enter hi(),而不会输出任何一个 exit hi()
# 第②种,有await,main协程在await处会挂起,事件循环才去理会执行完的sleep,
# 并恢复对应的那个hi协程,接着输出 exit hi(),然后返回
# 下面是具体情况:
# 第一种
# b = await tasks[0] # await第1个协程,1秒后第1个协程返回(其余协程的sleep还未结束),输出第一个exit hi()
# print('b is:', b) # b结果为第1个协程(即tasks[0])的返回值,即返回1
# 第二种
# b = await tasks[1] # await第2个协程,2秒后第2个协程返回(此时第1个协程也已返回),输出两个exit hi()
# print('b is:', b) # b结果为第2个协程(即tasks[1])的返回值,即返回2
# 第三种
# b = await tasks[3] # await第4个协程,4秒后第4个协程返回(前三个协程也已返回),输出全部四个exit hi()
# print('b is:', b) # b结果为第4个协程(即tasks[3])的返回值,即返回4
# 第四种
# for t in tasks:
# 下面一行,先挂起,并把任务列表里的所有协程都执行起来,如上文所述情况②,然后等hi协程恢复并返回,并把返回值给b
# b = await t
# print('b is:', b)
print(f'======== main() sleep at datetime.datetime.now()')
await asyncio.sleep(3) # 这里,先执行asyncio.sleep(3),挂起main协程,然后把任务列表里的所有协程都执行起来
print(f'======== main() end at datetime.datetime.now()')
# 最后一种
# 如果整个main协程里没有await,在main退出后,即使把任务列表里的所有协程执行起来了,
# 也无法让某个hi协程里的await在sleep结束后,被事件循环恢复执行,
# 因为恢复链路是断掉的,asyncio.run启动事件循环(event_loop)后,从main协程到hi协程,到asyncio.sleep协程,
# 这整条协程链路是通的,才能被事件循环从main协程去唤起asyncio.sleep,并从hi一直返回回来,回到main协程
if __name__ == '__main__':
asyncio.run(main())
print('done')
运行结果:
======== main() begin at 2022-08-28 11:06:06.479365
======== main() sleep at 2022-08-28 11:06:06.479490
enter hi(), #1 @2022-08-28 11:06:06.479547
enter hi(), #2 @2022-08-28 11:06:06.479569
enter hi(), #3 @2022-08-28 11:06:06.479584
enter hi(), #4 @2022-08-28 11:06:06.479597
exit hi(), #1 @2022-08-28 11:06:07.480400
exit hi(), #2 @2022-08-28 11:06:08.480724
======== main() end at 2022-08-28 11:06:09.479725
exit hi(), #3 @2022-08-28 11:06:09.479774
done
什么时候用协程函数呢?
如果要进行IO操作(读写网络、读写文件、读写数据库等),就把它定义为协程函数。
如果要进行计算密集型操作,推荐用多进程,因为有GIL。
参考:
https://www.yuanrenxue.com/tricks/python-asyncio-usage-exmaple.html
以上是关于Python中的协程与asyncio原理的主要内容,如果未能解决你的问题,请参考以下文章
2020-08-20:GO语言中的协程与Python中的协程的区别?