Python并发之异步I/O(async,await)

前面关于yield,yield from的语法说了好多,其实大多是铺垫,算是python在通往异步大道上的一些探索,但是将异步完整融入python整个过程并非都是顺利的,创建修改替换修改,反反复复,直到最新几版的python,趋于平稳,用其优雅特性来给人们展示异步,我们一起来看看!

1. 背景

Python有很长一段的异步编程历史,特别是twistedgevent和一些无堆栈的Python项目。
异步编程因为一些好的原因在这些年来越来越受关注。尽管相对于传统的线性风格更难一点,但是却是值得的:因为异步编程有更高的效率。
举个例子:在Http请求方面,Python异步协程可以提交请求然后去做队列中其他等待的任务,让它慢慢请求,而不是传统的一直等它请求到完成为止,这样的话会浪费更多的时间与资源。总之异步编程能让你的代码在处于等待资源状态时处理其他任务。
在Python3.4中,asyncio产生了。而在Python3.5中,有加入了对async defawait新的语法支持。

2. 四个核心概念

2.1 Eventloop

Eventloop 可以说是 asyncio 应用的核心,是中央总控。Eventloop 实例提供了注册、取消和执行任务和回调的方法。
把一些异步函数 (就是任务,Task,一会就会说到) 注册到这个事件循环上,事件循环会循环执行这些函数 (但同时只能执行一个),当执行到某个函数时,如果它正在等待 I/O 返回,事件循环会暂停它的执行去执行其他的函数;当某个函数完成 I/O 后会恢复,下次循环到它的时候继续执行。因此,这些异步函数可以协同 (Cooperative) 运行:这就是事件循环的目标。

2.2 Coroutine

协程 (Coroutine) 本质上是一个函数,特点是在代码块中可以将执行权交给其他协程:

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
import asyncio

# 在Python 3.4中, 创建一个协程我们用asyncio.coroutine装饰器,但现在基本弃用
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())

#asyncio.run 是 Python 3.7 新加的接口,要不然你得这么写:
#loop = asyncio.get_event_loop()
#loop.run_until_complete(main())
#loop.close()

if __name__ == '__main__':
asyncio.run(main())

>>Suspending a
>>In b
>>Resuming a

asyncio.gather 用来并发运行任务,在这里表示协同的执行 a 和 b两个协程
在协程 a 中,有一句 await asyncio.sleep (0),await 表示调用协程,sleep 0 并不会真的 sleep(因为时间为 0),但是却可以把控制权交出去了。

2.3 Future

接着说 Future,它代表了一个「未来」对象,异步操作结束后会把最终结果设置到这个 Future 对象上。Future 是对协程的封装,不过日常开发基本是不需要直接用这个底层 Future 类的

2.4 Task

Eventloop 除了支持协程,还支持注册 Future 和 Task2 种类型的对象,那为什么要存在 Future 和 Task 这 2 种类型呢?
Future 是协程的封装,Future 对象提供了很多任务方法 (如完成后的回调、取消、设置任务结果等等),但是开发者并不需要直接操作 Future 这种底层对象,而是用 Future 的子类 Task 协同的调度协程以实现并发。

Task 非常容易创建和使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 或者用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

在这边的时候我卡了很长时间:future与Task的区别是什么????

future在多线程说过,future表示终将发生的事情,而确定某件事会发生的唯一方式是执行的时间已经排定。(你不排定它,它就是个协程),那怎么排定?

BaseEventLoop.create_task(…) 或者 asyncio.ensure_future方法接收一个协程,排定它的运行时间,然后返回一个asyncio.Task 实例——也是 asyncio.Future 类的实例,因为 Task 是Future 的子类,用于包装协程。这与调用 Executor.submit(…) 方法创建Future实例是一个道理

这句话我读了好多遍,意思是不是说future跟task是同一样东西。对于event loop来说,一个包装了协程的future,就是循环中的一个task?我是这么理解的。

我们无法确定future啥时完成结束,但是总归结束(无论报错还是返回值)的,因为我们已经给它排定了时间。

3. 如何准确使用asyncio

自从Python3.5加入了async defawait新的语法,在往后Python大版本更新中一直对该部分的语法有更新修改,从前期的乱而杂,到最新的Python3.8语法渐渐稳定下来,优雅了很多,文档也条理了很多,其实要使用真正的语法还是要多看看官网例子

初阶选手使用时,一般的High-level APIs完全可以满足需求,当理解了高阶API时,在去查看源代码,查看Low-level APIs,深层次的理解!这里有个较新的教程,分三个部分,很好!,推荐阅读!