Python asyncio 异步IO的理解
1、理解概念
asyncio 是用来编写并发代码的库,使用 async/await 语法。
(1)何为并发:
并发就是在一个时间段内,同时做多个事情。
比如在单CPU的机器中(只有一个CPU的机器),我们可以一边听歌,一边斗地主,一边聊QQ。
在我们看来,我们是同时在做这三件事,可是在单CPU中,这并不是同时进行的。
单CPU把自己分成了一段一段的时间片,每个时间片用来做一个事情。
如果该时间片用光了,但是事件还没有做完,那就先不做了。CPU先去做其他的,做完其他的事情,再利用新的时间片,来接着做这个事情。
因为时间片之间的切换速度是很快的,用户根本体验不出来,所以就感觉是同时在做这些事情。
以上就可以理解为并发。
(2)何为并行
基本上提到了并发,总有人要问什么是并行,以及并行与并发的区别。
并行就是同一时间点,同时做多个事情。听起来和并发差不多,但是还是有区别的,看完下面的解释就容易理解了。
并行在单CPU中是不可能存在的,因为并行是真真实实存在的。而并发是宏观上存在的,
比如在多CPU的机器中(有多个CPU的机器,与单CPU对立),我们可以一边听歌,一边斗地主,一边聊QQ。
在我们看来,我们是同时在做这三件事,在多CPU中,其实也是同时在做这三个事情
一个CPU负责听歌的进程,一个CPU负责斗地主的进程,一个CPU负责QQ的进程。
在物理时间上,CPU真的就是在同时做这三个事情,只是每个事件用的CPU不同而已。
以上可以理解为并行。
备注:我这里所说的多CPU,一个CPU负责一个进程,其实也可以理解为一个CPU有多个核,一个核负责一个进程,这样也是说的通的。
因为我对多CPU以及多核CPU还没有理解清楚,所以上面的举例就用了多CPU。本文主要是记录自己的理解,所以难免有不懂的地方。请包涵。
(3)并发与并行的共同点与区别
共同点:
我们可以理解并发与并行都是同一个时间,做多个事情,执行多个程序。
不同点:
并发是在宏观上存在的,是在逻辑上存在的,理解了(1),应该就能理解这句话了。这也是为什么用单CPU举例的意义所在。
并行是在物理上存在的,是真实存在的。理解了(2),应该就能理解这句话了。
单核CPU是不可能存在并行的。
单CPU中进程只能是并发,多CPU计算机中进程可以并行。
单CPU单核中线程只能并发,单CPU多核中线程可以并行。
(4)并发与并行学习链接
https://cloud.tencent.com/developer/article/1424249 图形并茂的,助于理解!
https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/ 义正言辞的!
https://sunweiguo.github.io/2019/11/17/miscellany/20%E5%85%B3%E4%BA%8E%E5%A4%9ACPU%E5%92%8C%E5%A4%9A%E6%A0%B8CPU%E7%9A%84%E5%8C%BA%E5%88%AB/ CPU知识点!
(5)协程理解
一般实现并发的方式就是使用多进程,多线程。这里加入一个协程概念,协程也可以实现并发。
asyncio就是通过协程,实现的并发。与本文第一句相互呼应,嘿嘿!
我们编写程序,正常执行是串行的,即从上而下依次执行。
比如定义一个函数,执行函数就是从函数的第一行,依次执行第二行,再执行第三行......最后执行最后一行,然后结束函数的执行。
协程就是执行一个函数,到某一行,然后转去执行其他函数,执行其他函数到某一行,又去执行了其他函数,最后在合适的时间点,又转回来执行最初的函数
在合适的时间点,指的是其他函数运行结束,或者其他函数主动释放CPU。
比如某个函数中有sleep操作,这个时间,等待睡眠是浪费CPU的,这个时候可以跳出该函数,转去执行其他函数,当其他函数执行完,再接着执行这个sleep的后一句
以上就是个人理解协程,比较空洞,结合下面的代码,就能容易理解一点了。
2、使用async关键字定义协程
def function(name): print('这是正常的函数,name:{}'.format(name)) async def coroutines_function(name): print('这是协程函数,name:{}'.format(name)) if __name__ == '__main__': function('hello') # 正常执行 coroutines_function('hello') # 报错 coroutine 'coroutines_function' was never awaited
3、通过asyncio运行协程(开始了asycnio的使用)
协程对象不能直接运行,需要添加到事件循环中,在合适的时间点,事件循环自动运行
合适的时间点指的是CPU分配时间片给他,其他程序不占用CPU
import asyncio # 导入异步io库 async def coroutines_function(name): print('这是协程函数,name:{}'.format(name)) if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回协程对象 print(type(obj)) # <class 'coroutine'> loop = asyncio.get_event_loop() # 获取事件循环 loop.run_until_complete(obj) # 注册到事件循环中 loop.close() # 关闭事件循环
运行
4、创建task对象
上面3中的示例代码 loop.run_until_complete(obj)
传入的参数是一个协程对象,传入后协程对象会被包装成task对象,这样运行的就是task对象,而不是协程对象
可以这么写,创建一个task对象,然后传给run_until_complete函数
import asyncio # 导入异步io库 async def coroutines_function(name): print('这是协程函数,name:{}'.format(name)) if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回协程对象 print(type(obj)) # <class 'coroutine'> loop = asyncio.get_event_loop() # 获取事件循环 task = loop.create_task(obj) # 创建task print(type(task)) # <class '_asyncio.Task'> loop.run_until_complete(task) # 注册到事件循环中 loop.close() # 关闭事件循环
运行
5、task对象的状态
task对象被创建出来时,不会被执行,这时候状态是pending
之后被添加到事件循环中,等待执行
执行结束后状态为finished
import asyncio # 导入异步io库 async def coroutines_function(name): print('这是协程函数,name:{}'.format(name)) if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回协程对象 print(type(obj)) # <class 'coroutine'> loop = asyncio.get_event_loop() # 获取事件循环 task = loop.create_task(obj) # 创建task print(type(task)) # <class '_asyncio.Task'> print(task) # <Task pending coro=<coroutines_function() loop.run_until_complete(task) # 注册到事件循环中 print(task) # <Task finished coro=<coroutines_function() done loop.close() # 关闭事件循环
运行
6、回调函数
可以为task对象添加一个回调函数,执行完task后,自动执行绑定的回调函数
import asyncio # 导入异步io库 async def coroutines_function(name): print('这是协程函数,name:{}'.format(name)) def callback(function): print('协程函数本身:{}'.format(function)) print('回调函数完成 ---> coroutines_function') if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回协程对象 loop = asyncio.get_event_loop() # 获取事件循环 task = loop.create_task(obj) # 创建task task.add_done_callback(callback) loop.run_until_complete(task) # 注册到事件循环中 loop.close() # 关闭事件循环
运行
7、获取协程函数的返回值
只有当协程函数执行完毕,状态为finished时,才可以获取返回值,提前获取报错
import asyncio # 导入异步io库 async def coroutines_function(name): print('这是协程函数,name:{}'.format(name)) return 'coroutines_function-->{}'.format(name) def callback(function): print('协程函数本身:{}'.format(function)) print('回调函数完成 ---> coroutines_function') if __name__ == '__main__': obj = coroutines_function('墨玉麒麟') # 返回协程对象 loop = asyncio.get_event_loop() # 获取事件循环 task = loop.create_task(obj) # 创建task # print(task.result()) # 报错 Result is not set. task.add_done_callback(callback) loop.run_until_complete(task) # 注册到事件循环中 print('task result:{}'.format(task.result())) # coroutines_function-->墨玉麒麟 loop.close() # 关闭事件循环
运行
8、await让出CPU控制器
以上的例子都不能演示并发的情景,因为以上的例子没有用到await关键字
我们可以通过使用await关键字,来让出CPU的控制器,去执行其他函数的语句,从而实现并发机制
当事件循环看到await关键字时,事件循环会挂起该协程,从而去执行其他协程。其他协程挂起时,或者其他协程执行完毕,事件循环再继续执行最初的协程
(1)举个睡眠的例子
import asyncio # 导入异步io库 import time async def sleep_second(second): print('将要睡眠{}秒'.format(second)) await asyncio.sleep(second) print('睡眠{}秒完成,醒过来了'.format(second)) return '休息了{}秒'.format(second) if __name__ == '__main__': start = time.time() obj1 = sleep_second(5) # 返回协程对象 obj3 = sleep_second(3) # 返回协程对象 obj5 = sleep_second(1) # 返回协程对象 loop = asyncio.get_event_loop() # 获取事件循环 task1 = loop.create_task(obj1) # 创建task task3 = loop.create_task(obj3) # 创建task task5 = loop.create_task(obj5) # 创建task loop.run_until_complete(task1) # 注册到事件循环中 loop.run_until_complete(task3) # 注册到事件循环中 loop.run_until_complete(task5) # 注册到事件循环中 loop.close() # 关闭事件循环 end = time.time() print('耗时:{}'.format(end - start))
运行
可以看出,当运行到睡眠5秒的时候,程序并没有等待,而是去运行睡眠3秒,再去运行睡眠1秒,最后1秒先执行完毕,先返回,再是睡眠3秒返回,再是睡眠5秒返回
用时是5秒,而不是5 + 3 + 1 = 9 秒
备注 await后面需要跟协程函数,而不能是普通函数,这里不能用time.sleep()代替asyncio.sleep(),用了会报错,可以自己换了尝试一下看看
(2)演示协程嵌套
import asyncio # 导入异步io库 import time async def fun1(name): print('fun1 name begin:{}'.format(name)) print('fun1 name end:{}'.format(name)) async def fun2(name): print('fun2 name begin:{}'.format(name)) await fun1('墨玉麒麟') print('fun2 name end:{}'.format(name)) async def fun3(name): print('fun3 name begin:{}'.format(name)) await fun2('张龙赵虎') print('fun3 name end:{}'.format(name)) if __name__ == '__main__': start = time.time() obj3 = fun3('神龙吸水') # 返回协程对象 loop = asyncio.get_event_loop() # 获取事件循环 task3 = loop.create_task(obj3) # 创建task loop.run_until_complete(task3) # 注册到事件循环中 loop.close() # 关闭事件循环 end = time.time() print('耗时:{}'.format(end - start))
运行
备注:实际项目中,一般asyncio.sleep()都是用其他协程的库来代替,比如aiohttp、 aiomysq等
aiohttp 的简单使用可以参考我的另一篇随笔 https://www.cnblogs.com/rainbow-tan/p/15130054.html
学习链接: https://www.jianshu.com/p/b5e347b3a17c