熬着熬着,让时间熬成想要的那个自己,一个美丽的人。
分类: Python/Ruby
2014-02-26 10:32:09
第3章中有句话总是重复:Twisted不会自动让你的代码异步或者不阻塞。
那Twisted到底干什么呢?它给通用网络编程、文件系统和已经计时活动提供了无阻塞基元,把操作系统暴露出来的非阻塞API隐藏起来。Twisted程序是基于事件的,运用回调链,构建方法不用于同步编程。Twisted提供Deferred抽象帮助管理回调链。
尽管TWisted编程运用了这个基于事件的模型,有时你会需要用线程或者进程。这涨覆盖了部分常用例子和相关的Twisted 接口。
Threads
在有些例子里,比如说,当你用了一个阻塞的第三方接口,你无法用回调链和Deferreds把这个你想要用到Twisted程序中的功能方法重构到异步的接口上。
你被一个阻塞的API卡住了,你无法运用,不然就会阻塞整个事件循环。为了应用它,你需要在线程中设置阻塞调用。Twisted提供了几个方法处理线程调用,包括:
callInThread
在自己的线程里面运行一个阻塞的函数
deferToThread
在自己的线程里面运行一个阻塞的函数并作为Deferred返回结果
练习中,deferToThread比callInThread更多的被用到,因为多数情况你需要一个统一的接口提供结果,Deferred就是Twisted程序里的这个接口。
Example10-1交错调用了一个非阻塞函数和一个阻塞函数,是通过deferToThread执行的。运用了一个简便的帮助类完成时间的问题:twisted.internet.task.LoopingCall。LoopingCall生成了一个函数和参数,执行了这个每次交互都提供给它的初始方法的函数。我们运用了另一个方法从任务模块deferLater来执行在每次一个特定时间后的函数,在Example4-10中已经应用到了。
我们可以看到Duck和Goose交替输出,通过运用threads.deferToThread我们可以产生一个阻塞函数调用而不会阻塞reactor的事件循环。
注意,reactor反应器管理时间事件,因此一旦reactor启动,LoopingCall只是重复了函数调用。
我们知道reactor管理Deferred的回调链,一旦事件完成就会触发回调链。如果修改了callLater行改为callLater(.5, finish),这样在blockingApiCall完全结束(sleep就要1s)前就强制关掉reactor会怎么样?
因为在Deferred被触发之前reactor已经停止,因此Goose就不会被输出。为了保证我们能等到在reactor被停止之前deferToThread能触发Deferred的回调链,我们可以设置回调链的reactor.stop,如Example10-2中所示。
Twisted提供了几个其他的方法线程里运行代码。这些比较少用,但是了解一下操作总不会是坏事:
callFromThread
从另一个线程,运行reactor线程里面的方法。用callFromThread从外部reactor线程调 用reactor接口。比如,用callFromThread适应一下情况:
l 从另一个线程通过transport写输出数据
l 从另一个线程激活一个自定义log 观察器
l 从另一个线程停止reactor
callMultipleThread
在同一个线程里运行一系列方法
blockingCallFronThread
在reactor线程里面运行给定函数,阻塞调用线程一直到这个函数运行完毕。如果函数返回一个Deferred,结果就会被当做它的同步相关:返回一个成功结果或者是触发一个调用线程失败的异常。
如果你需要一个API的接口来得到同步结果就可以用blockingCallFronThread。
Subprocesses 子进程
Twisted提供了一个平台独立的API通过reactor以非阻塞方式运行子进程,通过Deferred返回输出。这就是Twisted显示火候的地方:Twisted API接口类似于目前被禁止的命令标准库模块,已经被子进程模块代替了。
运行一个子进程并获得结果
如果你只是想运行一个进程并得到输出说是返回代码,Twisted很容易就能实现:getProcessOutput和getProcessValue。
Example10-3用getProcessOutput显示了一个简单的小例子实现远程管理server。从客户端获取命令,两处运行man
如同第2章节的一样,我们创建了一个protocol.Factory子类RunCommandFactory,这个工厂创建了我们自定义RunCommand 协议的实例,客户端可以连接到服务。客户端一旦发送单行数据,RunCommand调用子类LineReceiver。Server记录了reactor活动和每一个客户端的请求给输出。
当一行被收到,getProcessOutput生成一个子进程并返回一个Deferred,当整个进程结束的时候Deferred被触发。我们增加了一个回调给writeSuccessResponse,把命令输出写到潜在的传送然后终止连接。
子进程运行的所在环境可以通过getProcessOutput的可选参数来进行定制。全部签名是getProcessOutput(executable, args=(), env={}, path=None, reactor=None, errortoo=0)。因为上面我们设置了errortoo=True,stderr(比如说,一个客户端请求了一个不存在命令的帮助文档)跟随stdout由成功回调链通过。
运行一个命令并只返回代码,用getProcessValue。它跟getProcessOutput一样支持环境定制,并有一个几乎可验证的签名:getProcessValue(executable, args=(), env={}, path=None, reactor=None)。
定制进程协议
如果你想在生成字进程之外还有其他需求,并得到输出,你需要写一个IProcessProtocol实现器(实际上twisted.internet.protocol.ProcessProtocol的子类),由reactor.spawnProcess激活。包括把孩子进程的stdin写入数据、用重定向运行子进程,并给孩子进程发送信号。
ProcessProtocol用来写简单的客户端和server时,结构上跟Protocol子类类似。有一个connectionMade方法,也有给孩子进程的文件定义的receive和connection lost方法。协议回调链是由reactor通过spawnProcess来注册的,对于特定的孩子进程环境有相似但更为丰富的标签。
Example10-4 用一个自定义EchoProcessProtocol来运行Example6-3里面的echo server应用,在10s后杀掉server。
用python twistd_spawnecho.py运行,然后,另一个终端,连接到繁生的echo server,telnet localhost 8000. 输入文本会被回应过来。10s后,echo server终止,reactor停掉,也会结束父进程。
父进程的log可能会如下:
spawnProcess实现了最简单的IProcessProtocol的实现器的实例,可以运行的名字在这个例子就是twistd。这个例子也传递了几个命令行参数给twistd:-n表明没有daemonize,-y跟着要运行的TAC文件名字。
这个例子运用了ProcessProtocol的子类,为了解释清楚,重写了子类的大多数方法:
connectionMade
一旦进程开始方法就被调用,并设置传输来进行通信。数据被self.transport.write写进进程的stdin,你也可以用self.transport.writeToChild识别被写入的文件描述器。
outReceived
当进程的stdout的数据通过管道到达的时候后这个方法被调用。数据是被缓冲的,按块到达,因此直到processEnded被调用完计算数据最准确。errReceived相似的接收进程的stderr传输的数据。
inConnectionLost,outConnectionLost和errConnectionLost
一般情况下,当stdin、stdout和stderr文件描述器被关闭后才会调用这些方法,父进程可能用self.transport.closeStdin关闭stdin来通知子进程父进程不会再传输任何数据了,这反过来会激活inConnectionLost。当进程终止的时候这三个都会被正常关闭。
processExited和processEnded
processExited是在进程退出时调用。当所有的文件描述器都关闭后,processEnded是最后一个被激活的回调链。processEnded因此是非常适合让reactor终止。
为了解释给子进程发送信号,我们用了self.transport.signalProcess在连接中断10s后给server发送SIGTERM。假设进程没有被正常中断,尝试一下发送KILL看看会发生什么。
更多练习和下一步计划
这个章节讨论了如何在Twisted应用程序里面无阻塞地使用线程和子进程。
当你需要用一个阻塞的第三方API时,Twisted的应用程序里最常用线程解决。deferToThread在自己的线程执行了一个阻塞的函数并以Deferred返回结果,即使当应用其他库的时候也能提供一个持续的API。
相同的,Twisted听了一个平台独立的,基于Deferred的API来运行子进程,就如同reactor里的无阻塞事件,用spawnProcess和简便的方法比如getProcessOutput。自定义进程协议子类protocol.ProcessProtocol就跟Protocl实现网络客户端和服务器类似。