异步和高并发在一些服务器场景需求很多,从2013年起由 Python 之父 Guido 亲自操刀主持了Tulip(asyncio)项目的开发,使得 Python 具备了优雅的异步编程库。
简介 {#简介}
-
之前介绍了使用 Python Threading 实现异步编程,使用的是多线程的思路,语法不够优雅而且对于简单的工作来说还是太重了。
-
比较合适的程序运行单位叫做
协程
,协程能够在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提供性能。
协程 {#协程}
协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行。
-
Asyncio 并不能带来真正的并行(parallelism)。当然,因为 GIL(全局解释器锁)的存在,Python 的多线程也不能带来真正的并行。可交给 asyncio 执行的任务,称为协程(coroutine)。一个协程可以放弃执行,把机会让给其它协程(即 yield from 或 await)。
-
在Python中有多种方式可以实现协程,例如:
- greenlet,是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现);
- yield,生成器,借助生成器的特点也可以实现协程代码;
- asyncio,在 Python3.4 中引入的模块用于编写协程代码;
- async & awiat,在 Python3.5 中引入的两个关键字,结合 asyncio 模块可以更方便的编写协程代码。
目前python异步相关的主流技术是通过包含关键字async&await的async模块实现,因此我们重点关注 asyncio 模块。
asyncio {#asyncio}
asyncio 是 Python 3.4 版本引入的标准库,直接内置了对异步IO的支持。
代码示例 {#代码示例}
- 示例 1
- 示例 2
- 示例 3
输出:一次发送三个下载请求,同时下载,假如每次下载花费1s,完成任务仅需要1s 左右
async 关键字 {#async-关键字}
async & await
关键字在 Python 3.5 版本中正式引入,代替了asyncio.coroutine
装饰器,基于他编写的协程代码其实就是上一示例的加强版,让代码可以更加简便可读。
- 协程函数:定义函数时候由async关键字装饰的函数
async def 函数名
(定义协程) - 协程对象:执行协程函数得到的协程对象。
注意:执行协程函数只会创建协程对象,函数内部代码不会执行。如果想要运行协程函数内部代码,必须要将协程对象交给事件循环来处理。
- 协程能做的事情
- 等待一个 future 结束
- 等待另一个协程(产生一个结果,或引发一个异常)
- 产生一个结果给正在等它的协程
- 引发一个异常给正在等它的协程
await 关键字 {#await-关键字}
await + 可等待的对象(协程对象、Future、Task对象 -> IO等待),遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。
task 对象 {#task-对象}
Task 对象的作用是在事件循环中添加多个任务,用于并发调度协程,通过asyncio.create_task(协程对象)
的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。
future 回调 {#future-回调}
假如协程是一个 IO 的读操作,等它读完数据后,我们希望得到通知,以便下一步数据的处理。这一需求可以通过往 future 添加回调来实现。
多个协程 {#多个协程}
实际项目中,往往有多个协程,同时在一个 loop 里运行。为了把多个协程交给 loop,需要借助 asyncio.gather
函数。
官方文档:
或者先把协程存在列表里:
也可以传 futures 给它:
gather 起聚合的作用,把多个 futures 包装成单个 future,因为 loop.run_until_complete 只接受单个 future。
Close Loop {#Close-Loop}
以上示例都没有调用 loop.close,好像也没有什么问题。所以到底要不要调 loop.close 呢?
简单来说,loop 只要不关闭,就还可以再运行。:
但是如果关闭了,就不能再运行了:
建议调用 loop.close,以彻底清理 loop 对象防止误用。
多进程+asyncio {#多进程-asyncio}
由于python本身只能单线程,所以所谓的线程是通过线程锁实现的。现在必须要通过多进程实现更多的并发。
现在demo实现了使用多进程,每个进程都有一个asyncio
gather 起聚合的作用,把多个 futures 包装成单个 future,因为 loop.run_until_complete 只接受单个 future。
参考资料 {#参考资料}
文章链接:
https://www.zywvvd.com/notes/coding/python/asyncio/asyncio/