Python asyncio的用法(附带实例)
Python 3.4 引入了协程的概念,加入了 asyncio,以生成器对象为基础。Python3.5 又提供了 async/await 语法层面的支持,本节将基于 Python3.5 了解异步编程的概念及 asyncio 的用法。
在开始介绍之前,需要了解以下几个概念:
为了简化并更好地标识异步 IO,从 Python 3.5 开始就引入新的语法 async 和 await,让 coroutine 的代码更简洁易读。
解决 Python3.4 和 Python3.5 的差异只需要进行如下关键字的替换:
使用 asyncio 编写程序代码为:
运行结果为:
下图分别展示了单线程、多线程、协程运行图。随着时间的推移,三种模式分别有 3 个任务需要完成,每个任务都在等待 IO 操作时阻塞自身,IO 阻塞时间用灰色表示。

图 1 单线程、多线程、协程运行图
在单线程同步模型中,任务按照顺序执行。如果某个任务因为 IO 操作而阻塞,则其他所有的任务都必须等待,直到 IO 操作完成之后才能依次执行。在 IO 操作时,CPU 除了等待什么事也不能干,非常浪费 CPU 资源。
在多线程模型中,3 个任务分别在独立的线程中执行。这些线程由操作系统管理,在多核 CPU 上可以并行执行,在单核 CPU 上可以交错执行。当某个线程遇到 IO 操作阻塞时,其他线程可以继续执行。与同步模型相比,虽然这种方式更有效率,但开发者必须写代码保护共享资源。
在使用协程的异步 IO 模型中,3 个任务交错执行,处于同一个线程中,当其中一个协程遇到 IO 操作时,则跳转到其他协程继续执行,既没有浪费 CPU 资源,也不需要加锁等安全机制。
在开始介绍之前,需要了解以下几个概念:
- event_loop 事件循环:程序开启一个无限循环,开发者把一些函数注册到事件循环中,当满足事件发生的条件时,调用相应的协程函数;
- coroutine 协程:协程对象,指一个使用 async 关键字定义的函数,它的调用不会立即执行函数,而是返回一个协程对象。协程对象需要注册到事件循环中,由事件循环调用;
- task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步的封装,其中包含任务的各种状态;
- future:代表将来执行或没有执行任务的结果,与 task 没有本质的区别;
- async/await 关键字:Python3.5 用于定义协程的关键字,async 定义一个协程,await 用于挂起阻塞的异步调用接口。
Python asyncio用法实例
Python3.4 首先使用 asyncio 提供的 @asyncio.coroutine 把一个 generator(生成器)标记为 coroutine(协程)类型,然后在 coroutine 内部用 yield from 调用另一个 coroutine 实现异步操作。为了简化并更好地标识异步 IO,从 Python 3.5 开始就引入新的语法 async 和 await,让 coroutine 的代码更简洁易读。
解决 Python3.4 和 Python3.5 的差异只需要进行如下关键字的替换:
@asyncio.coroutine (Python3.4)<==>async (Python3.5); yield from (Python3.4)<==>await (Python3.5).
使用 asyncio 编写程序代码为:
import asyncio import time now = lambda: time.time() async def func(x): print('Waiting for %d s' % x) await asyncio.sleep(x) return 'Done after {} s'.format(x) start = now() coro1 = func(1) coro2 = func(2) coro3 = func(4) tasks = [ asyncio.ensure_future(coro1), asyncio.ensure_future(coro2), asyncio.ensure_future(coro3) ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) for task in tasks: print("Task return: ", task.result()) print('Program consumes: %f s' % (now() - start))分析程序代码如下:
- 导入 asyncio 和 time 模块,定义一个计时的 lambda 表达式;
- 通过 async 关键字定义一个协程函数 func(),分别定义 3 个协程;
- 在 func() 内部使用 sleep 模拟 IO 的耗时操作,遇到耗时操作时,await 将协程的控制权让出;
- 定义一个 tasks 列表,列表中分别通过 ensure_future() 创建 3 个 task;
- 协程不能直接运行,需要将其加入事件循环,get_event_loop() 用于创建一个事件循环;
- 通过 run_until_complete() 将 tasks 列表加入事件循环中;
- 通过 task 的 result 方法获取协程运行状态;
- 计算整个程序的运行耗时。
运行结果为:
Waiting for 1 s Waiting for 2 s Waiting for 4 s Task return: Done after 1s Task return: Done after 2s Task return: Done after 4s Program consumes: 4.005412 s程序的总运行时间比 4 秒多一点,如果是同步模型,那么执行时间至少为 7 秒。
下图分别展示了单线程、多线程、协程运行图。随着时间的推移,三种模式分别有 3 个任务需要完成,每个任务都在等待 IO 操作时阻塞自身,IO 阻塞时间用灰色表示。

图 1 单线程、多线程、协程运行图
在单线程同步模型中,任务按照顺序执行。如果某个任务因为 IO 操作而阻塞,则其他所有的任务都必须等待,直到 IO 操作完成之后才能依次执行。在 IO 操作时,CPU 除了等待什么事也不能干,非常浪费 CPU 资源。
在多线程模型中,3 个任务分别在独立的线程中执行。这些线程由操作系统管理,在多核 CPU 上可以并行执行,在单核 CPU 上可以交错执行。当某个线程遇到 IO 操作阻塞时,其他线程可以继续执行。与同步模型相比,虽然这种方式更有效率,但开发者必须写代码保护共享资源。
在使用协程的异步 IO 模型中,3 个任务交错执行,处于同一个线程中,当其中一个协程遇到 IO 操作时,则跳转到其他协程继续执行,既没有浪费 CPU 资源,也不需要加锁等安全机制。