内容简介:这几天看asyncio相关的pycon视频又重温了asyncio 的官方文档,收获很多。之前asyncio被吐槽的一点就是文档写的不好,Python 3.7 时 asyncio 的官方文档被 Andrew Svetlov 以及 Yury Selivanov 等核心开发者重写了,新的版本我觉得已经好很多了。在这里记录一下我对asyncio的一些理解。asyncio里面主要有4个需要关注的基本概念Eventloop可以说是asyncio应用的核心,是中央总控。Eventloop实例提供了注册、取消和执行任务和回
前言
这几天看asyncio相关的pycon视频又重温了asyncio 的官方文档,收获很多。之前asyncio被吐槽的一点就是文档写的不好,Python 3.7 时 asyncio 的官方文档被 Andrew Svetlov 以及 Yury Selivanov 等核心开发者重写了,新的版本我觉得已经好很多了。在这里记录一下我对asyncio的一些理解。
核心概念
asyncio里面主要有4个需要关注的基本概念
Eventloop
Eventloop可以说是asyncio应用的核心,是中央总控。Eventloop实例提供了注册、取消和执行任务和回调的方法。
把一些异步函数(就是任务,Task,一会就会说到)注册到这个事件循环上,事件循环会循环执行这些函数(但同时只能执行一个),当执行到某个函数时,如果它正在等待I/O返回,事件循环会暂停它的执行去执行其他的函数;当某个函数完成I/O后会恢复,下次循环到它的时候继续执行。因此,这些异步函数可以协同(Cooperative)运行:这就是事件循环的目标。
Coroutine
协程(Coroutine)本质上是一个函数,特点是在代码块中可以将执行权交给其他协程:
❯ cat coro1.py import asyncio async def a(): print('Suspending a') await asyncio.sleep(0) print('Resuming a') async def b(): print('In b') async def main(): await asyncio.gather(a(), b()) if __name__ == '__main__': asyncio.run(main())
这里面有4个重要关键点:
async def await asyncio.sleep(0)
loop = asyncio.get_event_loop() loop.run_until_complete(main()) loop.close()
好了,我们先运行一下看看:
❯ python coro1.py Suspending a In b Resuming a
看到了吧,在并发执行中,协程a被挂起又恢复过。
Future
接着说Future,它代表了一个「未来」对象,异步操作结束后会把最终结果设置到这个Future对象上。Future是对协程的封装,不过日常开发基本是不需要直接用这个底层Future类的。我在这里只是演示一下:
In : def c(): ...: print('Inner C') ...: return 12 ...: In : future = loop.run_in_executor(None, c) # 这里没用await,None 表示默认的 executor Inner C In : future # 虽然c已经执行了,但是状态还是 pending。 Out: <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/lib/python3.7/asyncio/futures.py:348]> In : future.done() # 还没有完成 Out: False In : for a in dir(future): ...: if not a.startswith('_'): ...: print(a) ...: add_done_callback cancel cancelled done exception get_loop remove_done_callback result set_exception set_result
可以对这个Future实例添加完成后的回调(add_done_callback)、取消任务(cancel)、设置最终结果(set_result)、设置异常(如果有的话,set_exception)等。现在我们让Future完成:
In : await future Out: 12 In : future Out: <Future finished result=12> In : future.done() Out: True In : future.result() Out: 12
看到了吧,await之后状态成了finished。这里顺便说一下,一个对象怎么样就可以被await(或者说怎么样就成了一个awaitable对象)呢?给类实现一个__await__方法,Python版本的Future的实现大概如下:
def __await_(self): if not self.done(): self._asyncio_future_blocking = True yield self if not self.done(): raise RuntimeError("await wasn't used with future") return self.result() # May raise too.
这样就可以 await future
了,那为什么 await future
后Future的状态就能改变呢,这是因为用 loop.run_in_executor
创建的Future注册了一个回调(通过 asyncio.futures.wrap_future
,加了一个 _call_set_state
回调, 有兴趣的可以通过延伸阅读链接2找上下文)。
__await__
里面的 yield self
不要奇怪,主要是为了兼容 __iter__
,给旧的 yield from
用:
In : future = loop.run_in_executor(None, c) Inner C In : future Out: <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/lib/python3.7/asyncio/futures.py:348]> In : def spam(): ...: yield from future ...: In : s = spam() In : next(s) Out: <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/lib/python3.7/asyncio/futures.py:348]>
新的替代 yield from
的用法await必须在异步函数(用 async def申明)中使用:
In : def spam(): ...: await future ...: File "cell_name", line 5 SyntaxError: 'await' outside async function
Task
Eventloop除了支持协程,还支持注册Future和Task2种类型的对象,那为什么要存在Future和Task这2种类型呢?
先回忆前面的例子,Future是协程的封装,Future对象提供了很多任务方法(如完成后的回调、取消、设置任务结果等等),但是开发者并不需要直接操作Future这种底层对象,而是用Future的子类Task协同的调度协程以实现并发。
Task非常容易创建和使用:
# 或者用task = loop.create_task(a()) In : task = asyncio.ensure_future(a()) In : task Out: <Task pending coro=<a() running at /Users/dongwm/mp/2019-05-22/coro1.py:4>> In : task.done() Out: False In : await task Suspending a Resuming a In : task Out: <Task finished coro=<a() done, defined at /Users/dongwm/mp/2019-05-22/coro1.py:4> result=None> In : task.done() Out: True
asyncio并发的正确/错误姿势
在代码中使用async/await是不是就能发挥asyncio的并发优势么,其实是不对的,我们先看个例子:
async def a(): print('Suspending a') await asyncio.sleep(3) print('Resuming a') async def b(): print('Suspending b') await asyncio.sleep(1) print('Resuming b') async def s1(): await a() await b()
有2个协程a和b,分别sleep1秒和3秒,如果协程可以并发执行,那么执行时间应该是sleep最大的那个值(3秒),现在它们都在s1协程里面被调用。大家先猜一下s1会运行几秒?
我们写个小程序验证一下:
def show_perf(func): print('*' * 20) start = time.perf_counter() asyncio.run(func()) print(f'{func.__name__} Cost: {time.perf_counter() - start}')
大家注意我这个时间计数用的方法,没有用time.time,而是用了 Python 3.3新增的time.perf_counter它是现在推荐的用法。我们在IPython里面验证下:
In : from coro2 import * In : show_perf(s1) ******************** Suspending a Resuming a Suspending b Resuming b s1 Cost: 4.009796932999961
看到了吧,4秒!!!,相当于串行的执行了(sleep 3 + 1)。这是错误的用法,应该怎么用呢,前面的asyncio.gather就可以:
async def c1(): await asyncio.gather(a(), b()) In : show_perf(c1) ******************** Suspending a Suspending b Resuming b Resuming a c1 Cost: 3.002452698999832
看到了吧,3秒!另外一个是asyncio.wait:
async def c2(): await asyncio.wait([a(), b()]) In : show_perf(c2) ... c2 Cost: 3.0066957049998564
同样是3秒。先别着急,gather和wait下篇文章还会继续对比。还有一个方案就是用asyncio.create_task:
async def c3(): task1 = asyncio.create_task(a()) task2 = asyncio.create_task(b()) await task1 await task2 async def c4(): task = asyncio.create_task(b()) await a() await task In : show_perf(c3) ... c3 Cost: 3.002332438999929 In : show_perf(c4) ... c4 Cost: 3.002270970000154
都是3秒。asyncio.create_task相当于把协程封装成Task。不过大家要注意一个错误的用法:
async def s2(): await asyncio.create_task(a()) await asyncio.create_task(b()) In : show_perf(s2) ... s2 Cost: 4.004671427999938
直接await task不会对并发有帮助*。asyncio.create_task是Python 3.7新增的高阶API, 是推荐的用法 ,其实你还可以用asyncio.ensure_future和loop.create_task:
async def c5(): task = asyncio.ensure_future(b()) await a() await task async def c6(): loop = asyncio.get_event_loop() task = loop.create_task(b()) await a() await task In : show_perf(c5) ... c5 Cost: 3.0033873750003295 In : show_perf(c6) ... c6 Cost: 3.006120122000084
到这里,我们一共看到2种错误的,6种正确的写法。你学到了么?
代码目录
本文代码可以在 mp项目 找到
延伸阅读
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【1】JavaScript 基础深入——数据类型深入理解与总结
- 深入理解java虚拟机(1) -- 理解HotSpot内存区域
- 深入理解 HTTPS
- 深入理解 HTTPS
- 深入理解 SecurityConfigurer
- 深入理解 HTTP 协议
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Music Recommendation and Discovery
Òscar Celma / Springer / 2010-9-7 / USD 49.95
With so much more music available these days, traditional ways of finding music have diminished. Today radio shows are often programmed by large corporations that create playlists drawn from a limited......一起来看看 《Music Recommendation and Discovery》 这本书的介绍吧!