Python async/await入门
在新版Python3.5中,引入了两个新关键字async和await,用于解决在Python异步编程中无法有效 区分yield生成器与异步的关系的问题。
异步是一个什么东西
异步的作用在于,对于Python这种拥有GIL的语言,某个线程在处理单个耗时较长的任务时(如I/O 读取,RESTful API调用)等操作时,不能有效的释放CPU资源,导致其他线程的等待时间随之增加。 异步的作用是,在等待这种花费大量时间的操作数,允许释放CPU资源执行其他的线程任务,从而提 高程序的执行效率。
3.4之前如何实现异步
在3.5版本以前的程序中,Python程序通常是使用yield作为一个判断是否进入异步操作的关键词。 比如在3.4.x版本中,我们可以用这样的一个例子来看一下(或者你也可以用一个Tornado的例子,这 样你的程序就也可以运行在2.7.x版本的Python中了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import time
import asyncio
@asyncio.coroutine
def slow_operation(n):
yield from asyncio.sleep(1)
print("Slow operation {} complete".format(n))
@asyncio.coroutine
def main():
start = time.time()
yield from asyncio.wait([
slow_operation(1),
slow_operation(2),
slow_operation(3),
])
end = time.time()
print('Complete in {} second(s)'.format(end-start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
|
执行结果如下:
1 2 3 4 5 6 7 |
➜ Desktop pyenv shell 3.4.3 ➜ Desktop python 3.4_asyncio.py ➜ Desktop python 3.4_asyncio.py Slow operation 2 complete Slow operation 1 complete Slow operation 3 complete Complete in 1.0008249282836914 second(s) |
如果你了解过yield,你会知道yield其实作用是用来生成一个生成器,而生成器的作用是用于输出一系列的结果。比如计算斐波那契数列:
1 2 3 4 5 6 7 8 9 |
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
for n in fab(5):
print n
|
其实这个在实际执行过程中,生成器并未实际执行,只有当调用.next()时才开始执行,并返回当前的迭代值。最后执行完成之后,则会抛出StopIteration异常,表示迭代完成。当然,这个异常在大多数循环情况下(比如for)并不需要手工处理。当然,你也可以选择使用下面手工的方法(注意:next是Python3中的内置函数,如果是Python2,请使用f.next()):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
➜ Desktop python Python 3.4.3 (default, Aug 14 2015, 11:21:11) [GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> def fab(max): ... n, a, b = 0, 0, 1 ... while n < max: ... yield b ... a, b = b, a + b ... n = n + 1 ... >>> f = fab(5) >>> next(f) 1 >>> next(f) 1 >>> next(f) 2 >>> next(f) 3 >>> next(f) 5 >>> next(f) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration |
上面的方法如果不使用yield,则换成一个支持iterable的可能更直观理解一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Fab(object):
def __init__(self, max):
self.max = max
self.n, self.a, self.b = 0, 0, 1
def __iter__(self):
return self
def next(self):
if self.n < self.max:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration()
|
在异步中,程序的ioloop会自动处理这一类的生成器,这一个样例可以参考一下Tornado的处理方式: tornado.gen.coroutine。
3.5版本异步功能
在3.5之后的版本里,程序提供了async和await关键字,这两个关键字可以替代类似tornado中的gen.coroutine/yield或者是asyncio.coroutine/yield的作用。
比如在上一节中的例子改造成为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import asyncio
import time
async def slow_operation(n):
await asyncio.sleep(1)
print("Slow operation {} complete".format(n))
async def main():
start = time.time()
await asyncio.wait([
slow_operation(1),
slow_operation(2),
slow_operation(3),
])
end = time.time()
print('Complete in {} second(s)'.format(end-start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
|
新的关键字会让我们的程序看上去更加清晰。不过从执行效率上看,现在还是相较3.4.3有一些退步:
1 2 3 4 5 6 7 8 9 10 11 |
➜ Desktop pyenv shell 3.5.0rc1 ➜ Desktop python 3.5_asyncio.py Slow operation 1 complete Slow operation 3 complete Slow operation 2 complete Complete in 1.0012218952178955 second(s) ➜ Desktop python 3.4_asyncio.py Slow operation 1 complete Slow operation 3 complete Slow operation 2 complete Complete in 1.0060911178588867 second(s) |
不过现在还只是RC版本,相信之后还是会有一些性能调优的可能性。
更多的开发场景
现在Tornado正在开发的4.3版本已经支持了Python3.5的async/await关键字,在之后的开发中,可以替代@gen.coroutine和yield用于开发。根据作者的描述,如果是运行在3.5版本的Python上,建议使用关键字而不是yield方式,这样可以有更好的执行效率。当然,如果3.5只是一个可选版本,相信在相当长的一段时间之内,你还是需要使用原有的执行方式。
潜在的坑
async和await在Python 3.5与3.6版本中并不是关键字,Python 3.7中会作为关键字。因此需要在现有的程序中替代掉已有的可能导致冲突的关键字。

浙公网安备 33010602011771号