Python3异步编程与Twisted框架介绍

概述

异步编程模型

在早期的计算机那里,我们可以看到只有单一的工作流(workflow),因此它也只能解决某类问题,当某个任务存在多个工作流时或者说它可以分解成多个工作流时,这种单一的方法就不能有效地解决问题了。当我们仔细观察现实世界的任务时,特别是大型任务时,就会发现它从来都不是完全顺序执行的,总有任务的某些部分是并行的,这是一种树状的工作流。发掘和利用并行性是提高任务执行效率的非常重要的手段,而计算机对现实世界的抽象也就无法回避现实世界存在的这样的基本问题。

计算机发展过程中引入了ECF(Exceptional Control Flow)机制,这种机制反映的是一种通用的思想,因此它存在于现代计算机内部的各个层次,比如:硬件层的中断信号、操作系统层的软中断信号、进程间信号通信和进程间切换等。C++/Java/Python 这些现代编程语言中tay/exception/catch机制也是同样思想的一种表现。ECF机制使得我们可以对现实世界更好的抽象与模拟,它反映了在计算机中引入我们对时间、空间、通信和事件的理解。

异步模型同样也是基于同样的思想,发掘和利用程序的并行性,特别是I/O类程序(I/O bound program)。参考krondo的 Twisted Introduction Part1 ,那里对该主题有更详细具体的描述。

Select 和 Poll

Unix/Linux 传统的非阻塞 IO编程中,select()poll()epoll() 函数是最重要的系统调用。但三个函数的性能是不一样的,Comparing and Evaluating epoll, select, and poll Event Mechanisms 文档对比了三者的性能。一般来说,当涉及的 fd 数量较少的时候,使用 select 是合适的;如果涉及的fd很多,如高性能网络服务器中,适合使用 epoll

非阻塞 IO 不完全等于异步 IO。如果要使用异步 IOUnix/Linux可以使用基于信号的异步通知机制。

AIO

Linux 的 AIO 是一种异常 IO 机制,有多种实现方式。其中一种实现是在用户空间的 glibc 库中实现的,它本质上是借用了多线程模型,用开启新的线程以同步的方法来做 I/O,新的 AIO 辅助线程以 pthread_cond_signal() 的形式进行线程间的同步。

Linux AIO 也可以由内核空间实现,相比而言,要比 glibc 实现的方案有更好的性能。在用户空间,一般要结合 libaio 进行内核 AIO 的系统调用。

选择正确的 I/O 模型对系统性能的影响很大,关于该问题可以参阅著名的 C10K 问题,它特指服务器同时支持成千上万客户端的问题。

Asyncio

python asyncio库是一种在单线程、单进程下的异步模型,它的用户可以显示控制(context switching)程序的工作流,特别是当有多个任务协作进行时。这种特性是指它可以停止程序某部分的执行(往往是该部分因等待I/O事件而阻塞),但保存其调用时的状态信息,然后在未来需要的时候重新进入这个状态,在这之前,主程序可以切换到其他任务。而在多线程与多进程的模型下,任务的切换由操作系统控制,用户无法对何时切换、切换的顺序等作出预测,另外线程或者进程间的通信和同步也是不小的编程负担。

在使用asyncio库以前,要首先理解什么是事件循环(event loop),循环是我们将时间引入计算机后必然的产物。在现实世界中,我们看待对象的方式都是和时间有关的,在不同的时间有不同的状态,在计算机中,要表现对象的状态随时间的变化,就必须引入状态变量以及对状态变量的访问和修改机制。在一个并发的程序系统中,不同部分都有着自己的状态,而且它们之间会相互影响,通过事件循环的方式我们可以将逻辑上的并行工作流映射到对现实世界抽象的单一的时间流中。

任何一个asyncio程序都必须要生成一个event loop对象,每一个任务由coroutine对象表示。我们将coroutine对象想象‘挂在’(hook)或者说注册到(register)event loop上,如果某个coroutine 阻塞了,event loop就会跳过它执行其他任务,但event loop会监控它的状态,等到它从阻塞中唤醒时就从阻塞的地方重新执行它。

coroutine之间可以形成链状结构,即某一个coroutine的数据依赖于另一个coroutine,这种结构可以使用await关键字实现。call_soon、call_at和call_later API可以在特定的时间执行某个callback函数,Future对象可以用来异步获取任务的执行结果,其本身可以注册一些回调函数,这些 回调函数会在Future对象的结果设置后被调用。在Twisted框架中,它也是有着非常重要的作用。关于event loop清晰详细的描述,krondo的文章Twisted Introduction 可以作为很好的参考。

asyncio虽然是单线程的应用,但它仍然是并发的程序,它同样要面对并发的同步问题。asyncio可以使用类似与线程和进程的同步原语(Synchronization Primitives),比如锁(Lock)、事件(Event)、条件变量(Condition)和安全队列(safed queueu)。

Twisted 框架

Twisted是事件驱动的网络框架,其实现了反映堆模式(reactor),并支持多种协议和服务。总结来说,Twisted是将程序构建在reactor驱动的一系列异步回调链上,所以可以认为reactor是最顶层的代码,而用户的代码却是最底层,这与同步程序正好相反。Twisted中异步函数通常会返回一个Deferred对象,它类似于python的coroutine与generator。一个Deferred对象代表了一个‘异步的结果’或者说‘结果还没有到来’,异步函数会负责(通过启动一系列事件)当结果到来时相应地激活(那些事件来激活Deferred)Deferred中的callback链或者errback链。和具体可参考Twisted Introduction