Part 3: Our Eye-beams Begin to Twist
原文:
作者:dave译者:noteditblog:时间:2011.05.24什么也不做,the twisted way
最后我们决定重新用twisted来实现我们异步的读诗的客户端.但首先让我们写一些twisted代码找找twisted 的感觉.下面是一个简单到屁的twisted程序,源码在
from twisted.internet import reactorreactor.run()
你可以像这样运行它:
python basic-twisted/simple.py
就像我们在第二部分所说的,twisted 是一个反应器模式的实现,包含一个反应器或者被叫做事件循环的对象,这个对象是twisted 程序的核心.在上面的程序中的第一行我们导入reactor,第二行告诉reactor去执行一个事件循环.
这个程序事实上什么也没做,你可以用Control-C停止他,否则的话他会永远执行下去.一般来说我们应该给事件循环一个或者多个文件描述符让事件循环来监测I/O,我们后面会讲到该怎么做,但现在反应器的事件循环是卡住的,记住反应器的事件循环不是一个不停循环的繁忙的循环,如果你可以监测cpu的使用情况,你可以发现这个reactor 是不耗费cpu资源的,这个反应器的事件循环会在图片五的顶部卡住,等待将要来的事件.这个程序仍然是一个相当简单的程序,完全没有吸引力是吧? 我们接下来会做点有趣的事,不过我们已经可以得出下面的结论:
- twsited 的reactor 不会自动开始运行,你可以让它运行起来通过reactor.run()
- 反应器的循环(reactor loop)和它开始运行的地方运行在同一线程内,在这种情况下,他运行在主线程内
- 一但事件循环运行起来,就会永远运行下去.这时reactor受程序控制
- 如果没有什么事情可做,反应器的循环不耗费CPU资源
- reactor 不用创建,直接import进来就可以用
最后一点需要解释一下,在twisted中, reactor 是单例模式的.在一个twisted程序中只有一个reactor object,在你用import导入的时候就被创建了.如果你打开的源码,你会发现只有很少的代码,reactor 的真正实现在.
twisted 中有多种reactor 的实现.在第二部分已经提到,select 仅仅是其中的一中实现方法,也是twisted默认使用的方法.twisted中也包含其他的reactor 的实现方式,比如使用poll 的方式来实现.
如果要使用其他的reactor,你必须要在导入twisted.internet.reactor前先安装一下,你可以这样来安装pollreactor:
from twisted.internet import pollreactorpollreactor.install()
如果你在导入twisted.internet.reactor之前没有安装其他的reactor 实现,twisted默认选择selectreactor.所以一般的不要在一个模块的上层导入reactor用来避免不经意的安装了默认的reactor,相反的,要在你要引用reactor 的时候再去导入它.
Note:自从写这些文章开始,twisted正在慢慢的转向允许多种reactor 同时存在的结构(应该是这样翻译),在计划中,一个reactor对象被传递的时候只作为一个引用而不是从一个模块中导入.Note:不是所有的操作系统都支持poll,如果你的系统不支持,上面的例子在你的系统跑不起来
现在我们可以用pollreactor重新实现我们的第一个 twisted 程序,你可以在看到源码: from twisted.internet import pollreactorpollreactor.install()from twisted.internet import reactorreactor.run()
好吧,我们又非常成功的实现了一个什么也不做的poll 循环.
我们在以后的教程中都会使用使用twisted 的默认的reactor,为了学习twisted的话,所有的reactor都执行相同的功能.Hello Twisted
下面让我们实现一个做点东西的twisted程序,下面的程序会打印两行消息:def hello(): print 'Hello from the reactor loop!' print 'Lately I feel like I\'m stuck in a rut.'from twisted.internet import reactorreactor.callWhenRunning(hello)print 'Starting the reactor.'reactor.run()
程序代码在,如果你运行的话,会看到以下的输出:
Starting the reactor.Hello from the reactor loop!Lately I feel like I'm stuck in a rut.
需要你手动杀死这个程序,因为当执行完print 之后程序会卡住.
hello 函数是在reactor 运行之后被调用的,这就意味这hello函数是被reactor调用的,所以twisted的代码必须可以调用我们的定义的函数,我们让callwhenrunning 传递一个hello函数的引用到twisted code,并触发callwhenrunning.但让必须在ractor运行之后才可以.
我们用术语callback来描述这种引用, 一个callback 是我们给twisted的一个函数的引用,twisted 会call back 在正确的时候.既然twisted 和我们的业务逻辑是分开的,reactor 和我们的业务逻辑通过callback一个函数来进行交互.我们可以看到下面的程序看到twisted怎样和我们的程序交互的:import tracebackdef stack(): print 'The python stack:' traceback.print_stack()from twisted.internet import reactorreactor.callWhenRunning(stack)reactor.run()
你可以在看到源代码,会输出一些像下面的东西:
The python stack:...reactor.run() <-- This is where we called the reactor...... <-- A bunch of Twisted function calls...traceback.print_stack() <-- The second line in the stack function
不要在意中间的一些twisted之间的调用,注意reactor.run() 和我们的callback之间的关系
callback 是做什么的twisted 不是唯一一个用callback 的reactor 框架, 其他的异步框架和也在用,还有一个GUI 比如GTK和QT,也是基于reactor循环的."反应器"系统的开发者们非常爱callback,但是要考虑到下面几点:
- 反应堆模式是单线程的
- 一个reactor的框架比如twisted实现了reactor循环所以我们的代码不要再去实现了
- 我们的代码仍旧需要被调用来实现我们的业务逻辑
- 既然整个程序在单线程内,reactor循环仍需要调用我们的代码
- reactor不能事先知道我们哪部分代码需要被调用
考虑到上面几点,callback就是必须的了
图片六描述了callback 的时候发生了什么
图片六图片六说明一些callback 的重要属性:
- 我们的callback 代码和twisted 循环运行在同一个线程中
- 当我们的callback在运行的时候,twisted 循环不运行
- 同上,当twisted 循环运行时,callback是不运行的
- reactor循环恢复,当我们的callback返回时
在一个callback中,twisted 循环可以看作是被阻塞住了.所以我们应该确保我们的callback代码不要花费很多时间.通常的,我们应该在我们的callback中避免阻塞的I/O操作.否则我们使用反应器模式的好处就不存在了.twisted 不会提供任何防范方法去阻止你的阻塞程序,我们必须确保在callback中不要做阻塞操作.不过最后你会发现,我们不用担心基本的网络I/O操作,因为我们可以让twisted去为我们实现异步通信.
其他的可能出现阻塞的操作包括从不是socket 文件描述符(比如pipe)中读写,或者等待一个子进程结束.怎样把阻塞的操作编程的非阻塞的操作跟你要完成什么有很大的关系,但是总会有twisted api 可以帮助实现它.记住,一些标准的python 函数本身就是阻塞模式的,没有方法转为非阻塞模式,比如,os.system 函数会阻塞直到子进程运行完成,他们就是那样工作的,所以在你用twisted的时候,你应该避免在你的代码中使用这些阻塞函数.
Goodbye Twisted你可以让twisted 的reactor 停止运行通过 reactor 的stop方法.但是一但reactor 被停止它不能被重启,所以在你的程序不得不停止的时候再去调用reactor 的stop方法.
曾经在邮件列表中讨论过让reactor变为可重启动的,你可以控制reactor 什么时候开始,什么时候停止.在twisted 8.2.0中,你仅仅可以启动reactor一次
下面有一个程序,,在5秒后停止reactor:
class Countdown(object): counter = 5 def count(self): if self.counter == 0: reactor.stop() else: print self.counter, '...' self.counter -= 1 reactor.callLater(1, self.count)from twisted.internet import reactorreactor.callWhenRunning(Countdown().count)print 'Start!'reactor.run()print 'Stop!'
这个程序用callLater API 注册了一个callback,这个callback作为callLater 的第二参数,第一个参数是等待多少秒然后你的callback才被执行.
twisted 是怎样在正确的实行去执行callback 的?既然这个程序没有监听任何的文件描述符,为什么也会在select循环中被卡住呢? 事实是,select 还有其他的类似的poll 也接收一个可选的timeout值,如果在一个timeout 的时间周期内没有任何一个文件描述符可以读写,select 也会返回.如果你传递给timeout 一个0值,你可以没有阻塞的监听文件描述符.
你可以把timeout 想象成一个事件循环正在等的事件(参照图片五),twisted 用timeout 来确保任何一个被定时了的callback可以在正确的时间运行.假如一个callback需要花费很长的时间,一个定时的callback会被顺延,所以twisted 的callLater 机制不能提供非常准确的时间保证.下面是这个程序的输出Start!5 ...4 ...3 ...2 ...1 ...Stop!
最后会输出一个stop表示reactor 已经退出了,现在有了一个可以自己的停止的程序了.
接招吧 (让我想起了take that 乐队)twisted既然twisted 以callback的方法来执行我们的方法,你会想如果一个callback抛出了异常怎么办.让我们试试吧,会在一个callback中抛出一个异常:def falldown(): raise Exception('I fall down.')def upagain(): print 'But I get up again.' reactor.stop()from twisted.internet import reactorreactor.callWhenRunning(falldown)reactor.callWhenRunning(upagain)print 'Starting the reactor.'reactor.run()
你会看到以下输出:
Starting the reactor.Traceback (most recent call last): ... # I removed most of the tracebackexceptions.Exception: I fall down.But I get up again.
注意第二个callback仍会在第一个callback之后运行,即使我们看到了很多的异常的追踪信息.如果你把reactor.stop()注释掉的话,这个程序会仍会继续运行下去,所以reactor 会继续运行下去即使我们的一个callback抛出了异常,
网络服务器对鲁棒性要求比较高,即使在运行过程中出现了一些bug也不能让服务器down掉,不是在说我们不用去处理我们的错误,如果你感觉后面还有人支持着你,你会感觉很棒.Poetry,please下面我们要twisted去获取点小诗啦,在第四部分,我们会实现一个twised 的异步的客户端