Python 进阶系列笔记文章链接:
Python 进阶学习笔记之一:内置常用类型及方法
Python 进阶学习笔记之二:常用数据类型(上)
Python 进阶学习笔记之三:常用数据类型(下)
Python 进阶学习笔记之四:高效迭代器工具
Python 进阶学习笔记之五:异步 IO
Python 进阶学习笔记之六:多线程编程
Python 进阶学习笔记之七:互联网支持
Python 进阶学习笔记之八:面向对象高级编程
Python 进阶学习笔记之九:IO 编程
Python 进阶学习笔记之十:一般加密支持
Python 进阶学习笔记之十一:日志支持
Python 进阶学习笔记之十二:数据压缩与归档
编程中,我们经常会遇到“并发”这个概念,目的是让软件能充分利用硬件资源,提高性能。并发的方式有多种,多线程,多进程,异步IO等。多线程和多进程更多应用于CPU密集型的场景,比如科学计算的时间都耗费在CPU上,利用多核CPU来分担计算任务。多线程和多进程之间的场景切换和通讯代价很高,不适合IO密集型的场景,而异步IO就是非常适合IO密集型的场景,比如网络爬虫和Web服务。
在计算机程序中,IO就是读写磁盘、读写网络的操作,这种读写速度比读写内存、CPU缓存慢得多,前者的耗时是后者的成千上万倍甚至更多。这就导致,IO密集型的场景99%以上的时间都花费在IO等待的时间上。异步IO就是把CPU从漫长的等待中解放出来的方法。这就可以大大提高我们写的软件系统的并发性。
asyncio - 异步 IO
asyncio 模块用于支持异步 IO 编程和并发编程,这个模块是从 3.4
版本引入的标准库,在后续的几个小版本中一直在增强,3.7 中也增加了一些高层次的 API,本文会略有介绍。关于并发编程,在任何高级语言中都是一门很深奥的知识分支,本笔记进作为入门知识,如果要深入研究,请参阅专门的专题或书籍。
Python 3.6 版本后 asyncio
模块已经代替了原来的 asyncore
模块(异步Sockt 处理)和 asynchat
模块(异步 Socket 指令),因此3.6+版本的 python 推荐使用此模块进行异步执行和异步通信的编码实现。
协程与任务
入门使用
在 Python 中,一个异步执行单元叫做 协程
,协程通过 async/await 语法进行声明,是编写异步应用的推荐方式。
简单示例:
import asyncio
async def main():
print('hello')
await asyncio.sleep(1)
print('world')
asyncio.run(main()) # 注意,上面的协程函数 main 直接调用并不会执行
要真正运行一个协程,asyncio 提供了三种主要机制:
asyncio.run()
函数用来运行最高层级的入口点 “main()” 函数 (参见上面的示例)- 等待一个协程,即用
await
来修饰一个协程,需要注意的是await
只能用于函数内部,因此上面的例子如果要改用await
的话,还需要稍加修改async def run_main(): await main() asyncio.run(run_main())
- 使用
asyncio.create_task()
函数用来并发运行多个协程import asyncio async def foo(thread_name): c = 1 while c < 5: print(f"{thread_name}: {c}") c = c + 1 await asyncio.sleep(1) async def main_run(): t1 = asyncio.create_task(foo("t1")) t2 = asyncio.create_task(foo("t2")) # 3.7+ 版本推荐使用 create_task 创建 task t3 = asyncio.ensure_future(foo("t3")) # 3.7 以前版本使用此方法创建 task await t1 await t2 await t3 asyncio.run(main_run())
task 的目的之一就是支持 并发 执行多个 协程,当一个协程通过 asyncio.create_task()
等函数被打包为一个 任务,该协程将自动排入日程准备立即运行。
上面列子中还需要关注的点:
asyncio.run(coro, *, debug=False)
:运行 asyncio 程序入口,如果 debug 参数为 True,事件循环将以调试模式运行。asyncio.create_task(coro)
:将 coro 协程 打包为一个 Task 排入日程准备执行。返回 Task 对象。此函数 在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的asyncio.ensure_future()
函数。asyncio.sleep(delay, result=None, *, loop=None)
: 阻塞 delay 指定的秒数,也就是 休眠 ,注意这个函数本身是个协程函数,需要使用await
进行调用。
并发运行任务
函数 asyncio.gather(*aws, loop=None, return_exceptions=False)
用来支持并发 task 的执行,第一个参数支持多个协程对象,如果所有可等待(协程)对象都成功完成,结果将是一个由所有返回值聚合而成的列表,结果值的顺序与 aws 中可等待对象的顺序一致。
示例:
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({i})...")
await asyncio.sleep(1)
f *= i
print(f"Task {name}: factorial({number}) = {f}")
async def main():
await asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
)
asyncio.run(main()