- 任务是计算集中型的(比如,求一个很大整数的所有因数),并且需要相当的CPU时间进行计算;或者
- 任务并不是计算集中型的,但是需要等待某些数据,以产生结果。
分类: Python/Ruby
2016-02-09 23:35:19
这篇文档介绍了异步编程模型,以及在Twisted中抽象出的Deferred——象征着“承诺了一定会有的”结果,并且可以把最终结果传递给处理函数(Python中实现了__call__()方法的对象都可以称之为“函数”,方法也是以函数的形式存在的,因此将所有“function”译作“函数”。——译者注)。
这篇文档适合于刚接触Twisted的读者,并且熟悉Python编程语言,至少从概念上熟悉网络的核心概念,诸如服务器、客户端和套接口。这篇文档将给你一个对并发编程的上层概观(交叉执行许多任务),以及Twisted的并发模型:非阻塞编码或者叫异步编码。
在讨论过包含有Deferred的并发模型之后,将介绍当函数返回了一个Deferred对象时,处理结果的方法。
要完成某些计算任务经常需要不少时间,其原因有两点:
网络编程的基本功能就是等待数据。想象你有一个函数,这个函数会总结一些信息并且作为电子邮件发送。函数需要连接到一个远程服务器、等待服务器的回应、检查服务器能否处理这封电子邮件、等待回应、发送电子邮件、等待确认信息,然后断开连接。
这其中任何一步都有可能占用很长时间。你的程序可能使用所有可能模型中最简单的一个——它实际上只是停下来等着数据的发送和接收,但在这种情况下,它有非常明显的基本限制:它不能同时发送多封电子邮件;并且在发送电子邮件的时候,它其实什么也做不了。
因此,除了最简单的之外,所有网络程序都会避免这种模型。另外还有许多不同的模型,它们都允许你的程序在等待数据以继续某个任务的同时,继续做手头上的其他任务,这些模型你都可以采纳。
编写网络程序有很多种办法,主要有这么几种:
上述第三种模型就是使用Twisted框架时的标准模型:非阻塞调用。
当在一个线程中处理多个连接时,调度就成为了应用程序的责任,而不是操作系统的。调度通常的实现方案是:当连接准备好读或是写时,调度系统会调用一个之前注册过的函数——通常被叫做异步,事件驱动或是基于回调的编程。
在这种模型下,之前发送电子邮件的函数应该是象这样的:
于我们最初阻塞的步骤相比,上面这种非阻塞的步骤有什么好处呢?当发送电子邮件的函数在连接建立之前无法继续的时候,程序的其他部分依然可以执行其他任务,比如说开始为其他电子邮件的连接执行类似的步骤。于是,整个程序就不会卡在等待一个连接的建立上。
callback(回调)是通知应用程序数据已经就绪的经典模型。应用程序在调用一个方法,试图获取一些数据时,同时提供一个callback函数,当数据就绪时,这个 callback函数会被调用,并且就绪的数据就是调用的参数之一。因此,应用程序应该在callback函数中,继续执行之前获取这些数据时,想要执行的任务(当时没能马上得到这些数据,所以当时无法执行这些任务——译者注)。
在同步编程中,一个函数会先请求数据,然后等待数据,最后处理它。在异步编程中,一个函数请求数据之后,会在数据准备就绪后,让外部库调用其callback函数。
Twisted使用Deferred对象来管理callback序列。作为Twisted库的“客户端”,应用程序将一连串函数添加到Deferred对象中,当异步请求的结果准备就绪时,这一连串函数将被按顺序调用(这一连串函数被称为一个callback序列,或是一条callback链),一起添加的还有另外一连串函数,当异步请求出现错误的时候,他们将被调用(称作一个errback序列,或是一条errback链)。异步库代码会在结果准备就绪时,调用第一个callback,或是在出现错误时,调用第一个errback,然后Deferred对象就会将callback或errback的返回结果传递给链中的下一个函数。
Deferred被设计用来帮助解决第二类并发问题——非计算集中型任务,并且延迟时间是可以估计的。等待硬盘访问,数据库访问和网络访问的函数都属于这一类,尽管时间延迟不尽相同。
Deferred被设计用来使Twisted程序可以无阻塞地等待数据,直到数据准备就绪。为了达到这个目的,Deferred为库与应用程序提供了一个简单的callback管理接口。库可以通过调用Deferred.callback来把准备就绪的数据传回给应用程序,或者调用Deferred.errback来报告一个错误。应用程序可以按它们希望的顺序,设置结果处理逻辑,以callback与errback的形式添加到Deferred对象中去。
使 CPU尽可能的有效率,是Deferred与该问题的其他解决方案背后的基本思路。如果一个任务在等待数据,与其让CPU(以及程序本身!)眼巴巴地等着 数据(对于进程,这通常叫做“阻塞”),不如让程序同时执行些别的操作,并且同时留意着某些信号——一旦数据准备就绪,程序就可以(但不是立即——译者 注)回到刚才的地方继续执行。
在Twisted里,一个函数返回一个Deferred对象,意味着它给调用者一个信号:它在等待数据。当数据准备就绪后,程序就会激活那个Deferred对象的callback链,来处理数据。
在之前我们发送电子邮件的例子中,父函数调用了一个连接远程服务器的函数。异步性要求这个连接函数必须不等待结果而直接返回,父函数才可以做其他事情。可是,父函数或者它的控制程序又是怎么知道连接尚未建立的呢?当连接建立后,它又是怎么使用连接的呢?
Twisted有一个对象可以作为这种情况的一个信号。连接函数返回一个twisted.internet.defer.Deferred对象,给出一个操作尚未完成的信号。
Deferred有两个目的。第一,它说“我是一个信号,只是通知你不管你刚才要我做的什么,结果还没有出来”。第二,你可以让Deferred在结果出来后执行你的东西。
添加一个callback——这就是你让Deferred在结果出来后执行你东西的办法,也就是让Deferred在结果出来后调用一个方法。
有一个Twisted库函数返回Deferred,就是twisted.web.client.getPage(这是一个异步获取网页的函数。——译者注)。在这个例子里,我们调用了getPage——它返回了一个Deferred,然后添加了一个callback来处理返回的页面内容——当然处理是发生在数据准备就绪之后:
from twisted.web.client import getPage from twisted.internet import reactor def printContents(contents): ''' 这就是“callback”函数,被添加到Deferred,当“承诺了一定会有的数据”准备就绪后, Deffered会调用它 ''' print "Deferred调用了printContents,内容如下:" print contents # 停止Twisted事件处理系统————这通常有更高层的办法 reactor.stop() # 调用getPage,它会马上返回一个Deferred————承诺一旦页面内容下载完了, # 就会把他们传给我们的callback们 deferred = getPage('') //一旦调用getPage下载页面完成,则立即调用callback对应的函数.即addCallback(printContents) 中的printContents函数. # 给Deferred添加一个callback————要求它在页面内容下载完后,调用printContents deferred.addCallback(printContents) # 启动Twisted事件处理系统,同样,这不是通常的办法 reactor.run()
添加两个callback是一种非常常见的Deferred用法。第一个callback的返回结果会传给第二个callback:
from twisted.web.client import getPage from twisted.internet import reactor def lowerCaseContents(contents): ''' 这是一个“callback”函数,被添加到Deferred,当“承诺了一定会有的数据”准备就绪后, Deffered会调用它。它把所有的数据变成小写 ''' return contents.lower() def printContents(contents): ''' 这是一个“callback”函数,在lowerCaseContents之后被添加到Deferred, Deferred会把lowerCaseContents的返回结果作为参数,调用这个callback ''' print contents reactor.stop() deferred = getPage('') # 向Deferred中添加两个callback————让Deferred在页面内容下载完之后执行 # lowerCaseContents,然后将其返回结果作为参数,调用printContents deferred.addCallback(lowerCaseContents) //当有多个addCallback的时候,第一个addCallback(lowerCaseContents)执行后的结果,会当作第二个addCallback(printContents) 函数的参数 deferred.addCallback(printContents) reactor.run()
正如异步函数会在其结果产生之前返回,在有可能检测到错误之前返回也是可以的:失败的连接,错误的数据,协议错误,等等。正如你可以将callback添加到Deferred,你也可以将错误处理逻辑(“errback”)添加到Deferred,当出现错误,数据不能正常取回时,Deferred会调用它:
from twisted.web.client import getPage from twisted.internet import reactor def errorHandler(error): ''' 这是一个“errback”函数,被添加到Deferred,当出现错误事件是,Deferred将会调用它 ''' # 这么处理错误并不是很实际,我们只是把它打出来: print "An error has occurred: <%s>" % str(error) # 然后我们停止整个处理过程: reactor.stop() def printContents(contents): ''' 这是一个“callback”函数,被添加到Deferred,Deferred会把页面内容作为参数调用它 ''' print contents reactor.stop() # 我们请求一个不存在的页面,来演示错误链 deferred = getPage('does-not-exist') # 向Deferred添加callback,以处理页面内容 deferred.addCallback(printContents) # 向Deferred添加errback,以处理任何错误 deferred.addErrback(errorHandler) reactor.run()
在这篇文档中,你应该: