在提到python的asyncio的时候,我们会联想到async/await,event loop,coroutine,selector,Task,Future等等相关的内容,那么asyncio到底是谁(asyncio本身),它从哪里来的(为什么需要asyncio),要到哪里去(它能够解决什么问题)呢?

asyncio是什么

python官方文档中,对于asyncio模块是这样描述的:

asyncio 是用来编写 并发 代码的库,使用 async/await 语法。

asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。

asyncio 往往是构建 IO 密集型和高层级 结构化 网络代码的最佳选择。

asyncio 提供一组 高层级 API 用于:

此外,还有一些 低层级 API 以支持 库和框架的开发者 实现:

很多人都说asyncio是python最具雄心壮志的库之一,我也同意这种说法,尤其在现在这样万物互联的时代,如果一门编程语言不能够提供一个完善的网络异步编程方案应对并发压力,将会很难得到开发者的青睐。

不同于golang的CSP模型不要通过共享内存来通信,我们应该使用通信来共享内存的设计和实践理念。asyncio通过事件循环Event loop来调度有限的cpu资源,利用async/await语法糖 + selectors模块有效避免阻塞获得并发能力。

asyncio从何而来

asyncio的大致发展历程可以在这里看到:History of asyncio

结合文章How the heck does async/await work in Python 3.5 asyncio模块发展至今的重要节点大概如下图所示:

现在我们可以轻易的在代码中写类似这样的代码:

1
2
3
4
async def do_stuff():
	await db_operte()		# time cost
	await mq_process()		# time cost
	await http_response()	# time cost

不用担心进程由于其中一些耗时较高的io操作而被阻塞离不开这些相关的概念:coroutineevent loopasync/await,下边稍微研究一下几个关键节点。

从生成器到coroutine

维基百科中,对于coroutine(协程)的解释如下:

协程是计算机程序的一类组件,推广了协作式多任务子程序,允许执行被挂起与被恢复。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程更适合于用来实现彼此熟悉的程序组件,如协作式多任务异常处理事件循环迭代器无限列表管道

其中,最为关键的作用就是允许执行被挂起与被恢复。那么python的协程从无到有的过程是什么样的呢?

PEP 255 引入生成器

时间回到2001年,随着PEP 255生成器被引入python2.2主要用来解决python中可迭代对象的惰性求值。

yield

不过直到python2.5才能通过关键字yield 来将代码执行片段暂停,这对于迭代器节省内存和用python来控制无限列表非常有用。

仅仅能够将代码执行片段暂停,节省内存,无限列表这些特性都仅仅是对python中可迭代对象的加强。

PEP 342

直到Coroutines via Enhanced Generators,这个提案最重要的是引入两个新的特性:

  1. send(),这就让程序员不仅仅能够将代码执行片段暂停,并且可以在特定的时刻通过send()恢复代码片段的执行或者通过send(param)可以将指定的参数传入,更好地控制代码的执行流程
  2. 允许yield可以在try/finally片段中使用,解决了协程中异常的捕获
  3. 对生成器增加新的方法trow(),解决了协程中异常的popup

PEP 380

python3.3在这个提案中,引入了yiled from让程序员能够更轻松地去实现生成器/协程,并且它能够让你更轻松地将一系列的协程串联起来。

PEP 3156

asyncio模块作为标准库引入,于此一起引入的除了event loop概念以及关于event loop的api,还有FutureTransportProtocols, 并且增加了对协程的流程控制。

PEP 492

引入async/await语法糖,让程序员异步编程就像写同步代码一样容易。

event loop

讲到这里,asyncio模块的使用已经和现在我们平常使用的场景比较相近了。那么问题来了,为什么使用asyncio就能够让python在web编程中提升并发能力呢?

这里就不得不引出event loop事件循环。事件循环在编程领域并不少见,简单来说,事件循环可以理解为一个调度器,它会关注指定的一些事件(比如文件描述符是否可读),在事件到达指定状态的时候,它负责帮忙执行另外一段指定的代码片段。

在python中,asyncio模块提供了event loop,在实际使用中,asyncio利用selectors模块来获取一个socket是否已经准备好读取/写入数据。这样,就可以在io阻塞的时候,event loop就可以自动的把cpu资源交给其他代码段执行,在io准备好之后,在进行数据的读取/写入。

asyncio生态

和一些其他python web开发者交流发现,目前使用asyncio的并不是很多,大家更多地还是使用flask、Django这些框架。不过我们公司一直在使用tornado+asyncio生态进行开发。我们常用到的一些库有以下这些:

  • Web框架:tornado5.1,tornado在5.0之后就默认使用了asyncio的event loop
  • mongo:motor
  • redis: aioredis
  • Rabbitmq: aio-pika

在出现一些第三方的中间件没有提供支持asyncio的驱动的时候,一般可以通过run_in_excutor来避免io操作对主进程的阻塞。

参考文章

  1. How the heck does async/await work in Python 3.5
  2. asyncio官方文档
  3. History of asyncio
  4. asyncio项目地址
  5. David Beazley’s Python Brasil 2015 keynote