Python Twisted系列教程3:初步認識Twisted


作者:dave@http://krondo.com/our-eye-beams-begin-to-twist/  譯者:楊曉偉(采用意譯)

可以從這里從頭開始閱讀這個系列。

 

twisted的方式實現前面的內容

最終我們將使用twisted的方式來重新實現我們前面的異步模式客戶端。不過,首先我們先稍微寫點簡單的twisted程序來認識一下twisted

最最簡單的twisted程序就是下面的代碼,其在twisted-intro目錄中的basic-twisted/simple.py中。

from twisted.internet import reactor
reactor.run()

可以用下面的命令來運行它:

python basic-twisted/simple.py

正如在第二部分所說的那樣,twisted是實現了Reactor模式的,因此它必然會有一個對象來代表這個reactor或者說是事件循環,而這正是twisted的核心。上面代碼的第一行引入了reactor,第二行開始啟動事件循環。

這個程序什么事情也不做。除非你通過ctrl+c來終止它,否則它會一直運行下去。正常情況下,我們需要給出事件循環或者文件描述符來監視I/O(連接到某個服務器上,比如說我們那個詩歌服務器)。后面我們會來介紹這部分內容,現在這里的reactor被卡住了。值得注意的是,這里並不是一個在不停運行的簡單循環。如果你在桌面上有個CPU性能查看器,可以發現這個循環體不會帶來任何性能損失。實際上,這個reactor被卡住在第二部分圖5的最頂端,等待永遠不會到來的事件發生(更具體點說是一個調用select函數,卻沒有監視任何文件描述符)

下面我們會讓這個程序豐富起來,不過事先要說幾個結論:

  1. Twisted的reactor只有通過調用reactor.run()來啟動。
  2. reactor循環是在其開始的進程中運行,也就是運行在主進程中。
  3. 一旦啟動,就會一直運行下去。reactor就會在程序的控制下(或者具體在一個啟動它的線程的控制下)。
  4. reactor循環並不會消耗任何CPU的資源。
  5. 並不需要顯式的創建reactor,只需要引入就OK了。

最后一條需要解釋清楚。在Twisted中,reactorSingleton(也是一種模式),即在一個程序中只能有一個reactor,並且只要你引入它就相應地創建一個。上面引入的方式這是twisted默認使用的方法,當然了,twisted還有其它可以引入reactor的方法。例如,可以使用twisted.internet.pollreactor中的系統調用來poll來代替select方法

若使用其它的reactor,需要在引入twisted.internet.reactor前安裝它。下面是安裝pollreactor的方法

from twisted.internet import pollreactor

pollreactor.install()

如果你沒有安裝其它特殊的reactor而引入了twisted.internet.reactor,那么Twisted會為你安裝selectreactor。正因為如此,習慣性做法不要在最頂層的模塊內引入reactor以避免安裝默認reactor,而是在你要使用reactor的區域內安裝。

下面是使用 pollreactor重寫上上面的程序,可以在basic-twisted/simple-poll.py文件中找到中找到:

from twited.internet import pollreactor

pollreactor.install()

from twisted.internet import reactor

reactor.run()

上面這段代碼同樣沒有做任何事情。

后面我們都會只使用默認的reactor,就單純為了學習來說 ,所有的不同的reactor做的事情都一樣。

 

你好,Twisted

我們得用Twisted來做什么吧。下面這段代碼在reactor循環開始后向終端打印一條消息:

def hello():

    print 'Hello from the reactor loop!'

    print 'Lately I feel like I\'m stuck in a rut.'

from twisted.internet import reactor 

reactor.callWhenRunning(hello)

print 'Starting the reactor.'

reactor.run()

這段代碼可以在basic-twisted/hello.py中找到。運行它,會得到如下結果:

Starting the reactor. 
Hello from the reactor loop!
Lately I feel like I'm stuck in a rut.

仍然需要你手動來關掉程序,因為它在打印完畢后就又卡住了。

值得注意的是,hello函數是在reactor啟動后被調用的。這意味是reactor調用的它,也就是說Twisted在調用我們的函數。我們通過調用reactorcallWhenRunning函數,並傳給它一個我們想調用函數的引用來實現hello函數的調用。當然,我們必須在啟動reactor之前完成這些工作。

我們使用回調來描述hello函數的引用。回調實際上就是交給Twisted(或者其它框架)的一個函數引用,這樣Twisted會在合適的時間調用這個函數引用指向的函數,具體到這個程序中,是在reactor啟動的時候調用。由於Twisted循環是獨立於我們的代碼,我們的業務代碼與reactor核心代碼的絕大多數交互都是通過使用TwistedAPIs回調我們的業務函數來實現的。

我們可以通過下面這段代碼來觀察Twisted是如何調用我們代碼的:

import traceback

def stack():

    print 'The python stack:'

    traceback.print_stack()

from twisted.internet import reactor

reactor.callWhenRunning(stack)

reactor.run()

這段代碼的文件是 basic-twisted/stack.py。不出意外,它的輸出是:

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()與我們自己的函數調用之間的關系即可。

 

有關回調的一些其它說明:

Twisted並不是唯一使用回調的框架。許多歷史悠久的框架都已在使用它。諸多GUI的框架也是基於回調來實現的,如GTKQT

交互式程序的編程人員特別喜歡回調。也許喜歡到想嫁給它。也許已經這樣做了。但下面這幾點值得我們仔細考慮下:

  1. reactor模式是單線程的。
  2. Twisted這種交互式模型已經實現了reactor循環,意味無需我們親自去實現它。
  3. 我們仍然需要框架來調用我們自己的代碼來完成業務邏輯。
  4. 因為在單線程中運行,要想跑我們自己的代碼,必須在reactor循環中調用它們。
  5. reactor事先並不知道調用我們代碼的哪個函數這樣的話,回調並不僅僅是一個可選項,而是游戲規則的一部分。

6說明了回調過程中發生的一切:

6 reactor啟用回調

6揭示了回調中的幾個重要特性:

  1. 我們的代碼與Twisted代碼運行在同一個進程中。
  2. 當我們的代碼運行時,Twisted代碼是處於暫停狀態的。
  3. 同樣,當Twisted代碼處於運行狀態時,我們的代碼處於暫停狀態。
  4. reactor事件循環會在我們的回調函數返回后恢復運行。

在一個回調函數執行過程中,實際上Twisted的循環被有效地阻塞在我們的代碼上。因此,因此我們應該確保回調函數不要浪費時間(盡快返回)。特別需要強調的是,我們應該盡量避免在回調函數中使用會阻塞I/O的函數。否則,我們將失去所有使用reactor所帶來的優勢。Twisted是不會采取特殊的預防措施來防止我們使用可阻塞的代碼的,這需要我們自己來確保上面的情況不會發生。正如我們實際所看到的一樣,對於普通網絡I/O的例子,由於我們讓Twisted替我們完成了異步通信,因此我們無需擔心上面的事情發生。

其它也可能會產生阻塞的操作是讀或寫一個非socket文件描述符(如管道)或者是等待一個子進程完成。

如何從阻塞轉換到非阻塞操作取決你具體的操作是什么,但是也有一些Twisted APIs會幫助你實現轉換。值得注意的是,很多標准的Python方法沒有辦法轉換為非阻塞方式。例如,os.system中的很多方法會在子進程完成前一直處於阻塞狀態。這也就是它工作的方式。所以當你使用Twisted時,避使用os.system

退出Twisted

原來我們可以使用reactorstop方法來停止Twistedreactor。但是一旦reactor停止就無法再啟動了。(Dave的意思是,停止就退出程序了),因此只有在你想退出程序時才執行這個操作。

下面是退出代碼,代碼文件是basic-twisted/countdown.py

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 reactor

reactor.callWhenRunning(Countdown().count)

print 'Start!'
reactor.run()
print 'Stop!'

在這個程序中使用了callLater函數為Twisted注冊了一個回調函數。callLater中的第二個參數是回調函數,第一個則是說明你希望在將來幾秒鍾時執行你的回調函數。那Twisted如何來在指定的時間執行我們安排好的的回調函數。由於程序並沒有監聽任何文件描述符,為什么它沒有像前那些程序那樣卡在select循環上?select函數,或者其它類似的函數,同樣會接納一個超時參數。如果在只提供一個超時參數值並且沒有可供I/O操作的文件描述符而超時時間到時,select函數同樣會返回。因此,如果設置一個0的超時參數,那么會無任何阻塞地立即檢查所有的文件描述符集。

你可以將超時作為圖5中循環等待中的一種事件來看待。並且Twisted使用超時事件來確保那些通過callLater函數注冊的延時回調在指定的時間執行。或者更確切的說,在指定時間的前后會執行。如果一個回調函數執行時間過長,那么下面的延時回調函數可能會被相應的后延執行。TwistedcallLater機制並不為硬實時系統提供任何時間上的保證。

下面是上面程序的輸出:

Start! 
5 ... 
4 ...
3 ... 
2 ...
1 ... 
Stop!

 

捕獲它,Twisted

由於Twisted經常會在回調中結束調用我們的代碼,因此你可能會想,如果我們的回調函數中出現異常會發生什么狀況。(Dave的意思是說,在結束我們的回調函數后會再次回到Twisted代碼中,若在我們的回調中發生異常,那是不是異常會跑到Twisted代碼中,而造成不可想象的后果 )讓我們來試試,在basic-twisted/exception.py中的程序會在一個回調函數中引發一個異常,但是這不會影響下一個回調:

 

def falldown():
    raise Exception('I fall down.')

def upagain():
    print 'But I get up again.'
    reactor.stop()

from twisted.internet import reactor

reactor.callWhenRunning(falldown)
reactor.callWhenRunning(upagain)

print 'Starting the reactor.'
reactor.run()

當你在命令行中運時,會有如下的輸出:

Starting the reactor. Traceback (most recent call last):
... # I removed most of the traceback
exceptions.Exception: I fall down.
But I get up again.

注意,盡管我們看到了因第一個回調函數引發異常而出現的跟蹤棧,第二個回調函數依然能夠執行。如果你將reactor.stop()注釋掉的話,程序會繼續運行下去。所以說,reactor並不會因為回調函數中出現失敗(雖然它會報告異常)而停止運行。

網絡服務器通常需要這種健壯的軟件。它們通常不希望由於一個隨機的Bug導致崩潰。也並不是說當我們發現自己的程序內部有問題時,就垂頭喪氣。只是想說Twisted能夠很好的從失敗的回調中返回並繼續執行。

請繼續講解詩歌服務器

現在,我們已經准備好利用Twisted來搭建我們的詩歌服務器。在第4部分,我們會實現我們的異步模式的詩歌服務器的Twisted版。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM