35

揭开 Asyncio 的神秘面纱 : 协程就是生成器?

 4 years ago
source link: https://www.tuicool.com/articles/IZfYvqv
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

vYfia2q.jpg!web

Cosven,运维开发。

GitHub: https://github.com/cosven

博客: zhihu.com/people/cosven

在第一篇文章『 揭开 asyncio 的神秘面纱 : 从 hello world 说起 』中, 我们提出一个问题:Python 协程和生成器行为非常类似,它们究竟是什么关系? 在这篇文章中,我们就来探索、解决这个疑问。

万事先 Google 一下: python coroutine generator . 我们可以搜到这个  PEP 342 -- Coroutines via Enhanced Generators ,看这个 PEP 的状态已经是 Final 了,说明官方已经接受了这个提议, 十有八九,Python 默认的协程就是基于 增强的生成器 来实现的。 继续浏览 Google 搜索结果,我们还可以看到一些 coroutine 相关的资料, 比如: PEP 492 -- Coroutines with async and await syntax .

这篇文章,我们就以这两个 PEP 为参考资料,学习协程与生成器的联系与区别。

喵一眼 PEP 492 : 不止有 async/await 语法

我们喵一眼 PEP 492,可以发现,async/await 语法是在 Python 3.5 版本加入的(包含 3.5)。 而在 3.4 的时候,asyncio 就已经可以正常工作了,也就是说,3.4 版本也提供了一种方式来声明协程:

>>> @asyncio.coroutine
... def hello_world():
... yield
... print('hello world')
...
>>> g_coro = hello_world()
>>> g_coro.send(None) # 启动生成器/协程
>>> g_coro.send(None) # 恢复生成器
hello world
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

从这个写法,我们就很容易看出,以前的协程就是在生成器函数上套了个 asyncio.coroutine 的装饰器。

这种使用装饰器定义的协程叫做: generator based coroutine 。 而使用  async/await 关键字定义的协程叫做  native coroutine ,这是新的也是更推荐的写法。 这两种写法定义出来的协程本质是一样的,只是叫法不一样。

注:在 Python web 开发中,我们经常会提到另外一个异步编程框架 gevent,gevent 的中 coroutine 是基于 greenlet 实现,greenlet 底层不是基于生成器的。在后续的文章中, 我们也会讲 greenlet 和 generator 在实现层面的差异。

阅读 PEP 342

在读 PEP 342 之前,我们简单了解下 PEP。

PEP,全名 Python Enhancement Proposal ,当开发者想往 Python 添加大的新特性之前, 开发者需要写一个 PEP 来详细的介绍这个特性。一个 PEP 往往包含这几个部分:

1、特性简介( Abstract/Introduction

2、为什么要添加这个新特性( Motivation

3、基本理论( Rationale

4、新特性的一些细节( Specification

5、…

下面,我们就来阅读 PEP 342 的动机部分

它先描述了协程常见的一个使用场景

Coroutines are a natural way of expressing many algorithms, such as simulations, games, asynchronous I/O, and other forms of event-driven programming or co-operative multitasking.

接着说 Python 的生成器功能已经很接近协程(言下之意是功能上还差点)

Python's generator functions are almost coroutines -- but not quite

然后讲了现在的生成器功能差在哪里

in that they allow pausing execution to produce a value, but do not provide for values or exceptions to be passed in when execution resumes. They also do not allow execution to be paused within the try portion of try/finally blocks, and therefore make it difficult for an aborted coroutine to clean up after itself.

1、现在的生成器虽然可以在暂停执行时吐出一个值,但是恢复生成器时,我们不能传入参数。 (言下之意是恢复协程时,应该需要支持传入参数)

2、现在的生成器不支持在 try block 中暂停(言下之意是协程应该要支持在 try block 中暂停)

读完这段文字,相信我们自己可以回答这么两个问题:

1、了解为什么 Python 的协程会基于生成器实现?

实现协程前,需要对生成器作什么改进?

2、说了这么多次协程,但是我们似乎还没有说过一个最基本的概念:什么是协程?

什么是协程?

coroutine 的定义有很多,Python 文档是这么写的:

Coroutines is a more generalized form of subroutines. Subroutines are entered at one point and exited at another point. Coroutines can be entered, exited, and resumed at many different points.

而 Unity 文档是这么写的:

A coroutine is a function that can suspend its execution (yield) until the given YieldInstruction finishes.

解读一下这两个定义:

一个协程是一个函数/子程序(可以认为函数和子程序是指一个东西)。这个函数可以暂停执行, 把执行权让给 YieldInstruction ,等  YieldInstruction 执行完成后,这个函数可以继续执行。 这个函数可以多次这样的暂停与继续。

注:这里的 YieldInstruction , 我们其实也可以简单理解为函数。

下面,我们就来看一些例子,来帮助我们理解这个概念。

像普通函数一样

协程可以像普通函数一样,一个协程调用另外一个协程并等待它返回。

>>> import asyncio
>>>
>>> async def hello_world():
...     print('hello world')
...
>>> async def job1():
...     print('job1 started...')
...     print('job1 paused')
...     await hello_world()
...     print('job1 resumed')
...     print('job1 finished')
...
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(job1())
job1 started...
job1 paused
hello world
job1 resumed
job1 finished

job1 调用 hello_world 并等待它返回。

比普通函数厉害

协程可以在“卡住”的时候可以干其它事情。

>>> async def long_task():
...     print('long task started')
...     await asyncio.sleep(1)
...     print('long task finished')
...
>>> loop.create_task(long_task())
<Task pending coro=<long_task() running at <stdin>:1>> >>> loop.create_task(job1())                                                                                                                                                                                                                            >>>>> loop.create_task(job1())
<Task pending coro=<job1() running at <stdin>:1>> >>>
>>> try:
...     loop.run_forever()
... except KeyboardInterrupt:
...     pass
...
long task started
job1 started...
job1 paused
hello world
job1 resumed
job1 finished
long task finished
^C>>> 

从这段程序的输出可以看出,程序本来是在执行 long task 协程,但由于 long task 要 await sleep 1 秒,于是 long task 自动暂停了,hello_world 协程自动开始执行, hello world 执行完之后,long task 继续执行。

生成器的暂停与恢复

协程可以暂停和恢复,生成器当然就也可以。下面来看一个生成器暂停、恢复的例子:

>>> def gen():
... print('generator started')
... print('generator paused')
... yield
... print('generator resumed')
... print('generator paused, again')
... yield
... print('generator resumed')
... print('generator finished')
...

>>> def main():
... g = gen()
... print('generator created')
... print('-----------------')
... g.send(None) # start generator
... print('-----------------')
... g.send(None) # resume generator
... print('-----------------')
... g.send(None) # resume generator
...
>>> main()
generator created
-----------------
generator started
generator paused
-----------------
generator resumed
generator paused, again
-----------------
generator resumed
generator finished
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in main
StopIteration

小结

在这篇文章中,我们以两个 PEP 为主要参考资料,了解到协程有两种定义的方法, 其中使用生成器形式定义的协程叫做 generator-based coroutine , 通过  async/await 声明的协程叫做  native coroutine ,两者底层实现都是生成器。接着, 我们阐述了协程的概念,从概念和例子出发,讲了协程和生成器最主要的特征:可以暂停执行和恢复执行。

至于标题中的问题:协程就是生成器?我想我们或许可以这样回答: coroutine is not geneartor but coroutine equals to (enhanced) generator .

话说看完本问,你有木有好奇:为了实现协程,生成器作了哪些增强呢?这个问题留给读者哈 ~ 阅读 PEP 342 -- Coroutines via Enhanced Generators 即可找到答案。

iyA7fu3.jpg!webJ3iymmi.jpg!web

长按关注V社北京

测试 技术 面试 DevOps

关注V社北京,关注测试,添加巨蜥小程序获取全量精品技术文章

iMb2MfR.jpg!web

关注我

每天进步一点点


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK