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中会作为关键字。因此需要在现有的程序中替代掉已有的可能导致冲突的关键字。

posted @ 2017-07-26 14:26  天涯海角路  阅读(495)  评论(0)    收藏  举报