概述
异步 IO 是一种并发编程设计,Python3.4 开始,已经有专门的标准库 asyncio 来支持异步 IO 操作。你可能会说,我知道并发用多线程,并行用多进程,这里面的知识已经够我掌握的了,异步 IO 又是个什么鬼?本文将会回答该问题,从而使你更加牢固地掌握 Python 的异步 IO 操作方法。
几个名词先解释下:异步:异步是什么意思?这不是一个严格的定义,从下面两个方面来理解:异步程序可以在等待其最终结果的同时“暂停”并让其他程序同时运行。
通过上述机制,异步代码有助于并发执行。换句话说,异步代码表现出了并发的特点。
异步IO:一种与语言无关的范例(模型) ,很多编程语言都有这种实现,它是一种单线程,单进程设计:它使用协作多任务处理,尽管在单个进程中使用单个线程,异步 IO 仍具有并发的感觉。async/await:两个用于定义协程的新 Python 关键字。asyncio:Python 标准库,为运行和管理协程提供了基础和 API。
1、同步和异步的区别:
假设你去参加象棋比赛,有以下条件:24 个对手
在 5 秒内使每盘棋移动
对手各花费 55 秒采取行动
游戏平均 30 对动作(总共 60 个动作)
同步:你一次只能和一个对手下棋,在一局比赛结束前不能进入下一个。每个游戏需要(55 + 5) x 30 == 1800 秒或 30 分钟。整个展览需要 24 * 30 == 720 分钟或 12 个小时。异步:你在棋盘之间移动,在每个棋盘上移动一步,离开桌子,让对手在等待时间内和另一个对手下棋。在所有 24 场比赛中,一动需要 24 x 5 == 120 秒或 2 分钟。现在整个展览减少到 120 x 30 == 3600 秒,或仅 1 小时你只有两只手,一次只能移动一步。但是异步的方法可以从 12 小时减少到 1 小时。因此,协作式多任务处理是一种奇特的方式,可以说程序的事件循环与多个任务进行通信,以使每个任务在最佳时间轮流运行。
2、异步并不简单
网上流传这样的话:当必须使用多线程时就使用多线程,否则都尽可能使用异步 IO 。构建健壮的多线程程序是困难的且容易出错,异步 IO 避免了线程设计可能会遇到的某些潜在速度瓶颈。这并不是说写异步 IO 代码是简单的,请注意:当你进入底层时,异步编程也可能会很困难!Python 的异步模型是基于诸如回调,事件,传输,协议和期程( futures ) 之类的概念构建的,这些术语可能令人生畏。幸运的是,asyncio 已经发展的非常成熟,它的大多数功能不再是临时的,文档已得到了巨大的改进,与此相关的一些优质资源也开始出现。
3、async/await 语法及协程
异步 IO 的核心是协程。协程是一种特殊的 Python 函数,可以在到达返回值之前暂停其执行,并且可以将控制权间接传递给另一个协程一段时间。了解协程最简单的方法就是写一个 hello world 的代码来感受一下:
#!/usr/bin/env python3
# countasync.py
import asyncio
async def count() :
print("One")
await asyncio.sleep(1)
print("Two")
async def main() :
await asyncio.gather(count() , count() , count())
if __name__ == "__main__":
import time
s = time.perf_counter()
asyncio.run(main())
elapsed = time.perf_counter() - s
print(f"{__file__} executed in {elapsed:0.2f} seconds.")
执行结果:
One
One
One
Two
Two
Two
e:countasync.py executed in 1.01 seconds.
这里使用 time.sleep() 和 asyncio.sleep() 是有区别的,time.sleep() 可以表示任何耗时的阻塞函数调用,而 asyncio.sleep 不阻塞,可将 CPU 的控制权交给下一个协程。
4、Async IO 的规则
理解了 async,await 的规则,对掌握异步/等待功能非常重要。关键字 async def 可以定义一个协程函数或一个异步生成器函数。关键字 await 将功能控制传回事件循环。比如:
async def g() :
# Pause here and come back to g() when f() is ready
r = await f()
return r
这里的 await 挂起了本次协程的执行。如果 Python 在 g() 范围内遇到 await f() 表达式,那就意味着,“暂停 g() 的执行,直到我等待f() 返回结果。同时,让其他协程运行。”。当然也有一些规则要求什么地方可以使用 async/await 关键字,什么地方不能用:使用 async def 定义的函数是一个协程,它内部可以使用 await,return,yield,也可以都不用。使用 wait 或 return 创建一个coroutine函数。要调用 coroutine 函数,你必须使用 await 关键字。
很少情况下会在 async del 的代码块中使用 yield ,如果用了,会产生一个异步的生成器。
任何 async def 内都不能使用 yield from,会抛出语法错误。
就像不能在函数外面使用 yield 一样,也不能在 async def 外使用 await。会抛出语法错误。
下面是一些例子:
async def f(x) :
y = await z(x) # OK - `await` and `return` allowed in coroutines
return y
async def g(x) :
yield x # OK - this is an async generator
async def m(x) :
yield from gen(x) # No - SyntaxError
def m(x) :
y = await z(x) # Still no - SyntaxError (no `async def` here)
return y
言归正传,让我们来看一个更复杂的例子:给定一个产生随机数的函数 makerandom,它产生一个 0-9 之间的随机数,直到超过给定数据后结束,让此协程的多个调用无需等待彼此即可执行,代码如下:
import asyncio
import random
# ANSI colors
c = (
"