Twisted異步編程
這篇文檔介紹了異步編程模型,以及在Twisted中抽象出的Deferred——象徵着「承諾了必定會有的」結果,而且能夠把最終結果傳遞給處理函數(Python中實現了__call__()
方法的對象均可以稱之爲「函數」,方法也是以函數的形式存在的,所以將全部「function」譯做「函數」。——譯者注)。html
這篇文檔適合於剛接觸Twisted的讀者,而且熟悉Python編程語言,至少從概念上熟悉網絡的核心概念,諸如服務器、客戶端和套接口。這篇文檔將給你一個對併發編程的上層概觀(交叉執行許多任務),以及Twisted的併發模型:非阻塞編碼或者叫異步編碼。python
在討論過包含有Deferred的併發模型以後,將介紹當函數返回了一個Deferred對象時,處理結果的方法。react
併發編程介紹
要完成某些計算任務常常須要很多時間,其緣由有兩點:web
- 任務是計算集中型的(好比,求一個很大整數的全部因數),而且須要至關的CPU時間進行計算;或者
- 任務並非計算集中型的,可是須要等待某些數據,以產生結果。
等待迴應
網絡編程的基本功能就是等待數據。想象你有一個函數,這個函數會總結一些信息而且做爲電子郵件發送。函數須要鏈接到一個遠程服務器、等待服務器的迴應、檢查服務器可否處理這封電子郵件、等待迴應、發送電子郵件、等待確認信息,而後斷開鏈接。數據庫
這其中任何一步都有可能佔用很長時間。你的程序可能使用全部可能模型中最簡單的一個——它實際上只是停下來等着數據的發送和接收,但在這種狀況下,它有很是明顯的基本限制:它不能同時發送多封電子郵件;而且在發送電子郵件的時候,它其實什麼也作不了。編程
所以,除了最簡單的以外,全部網絡程序都會避免這種模型。另外還有許多不一樣的模型,它們都容許你的程序在等待數據以繼續某個任務的同時,繼續作手頭上的其餘任務,這些模型你均可以採納。api
不等待數據
編寫網絡程序有不少種辦法,主要有這麼幾種:服務器
- 在不一樣的操做系統進程中處理各個鏈接,這種狀況下,操做系統會處理進程調度,好比當一個進程等待的時候,讓其餘進程繼續工做;
- 在不一樣的線程中處理各個鏈接1,這種狀況下,線程框架會處理好諸如「當一個線程等待時,讓其餘線程繼續工做」的問題;或者
- 在一個線程中,使用非阻塞的系統調用來處理全部鏈接。
非阻塞調用
上述第三種模型就是使用Twisted框架時的標準模型:非阻塞調用。網絡
當在一個線程中處理多個鏈接時,調度就成爲了應用程序的責任,而不是操做系統的。調度一般的實現方案是:當鏈接準備好讀或是寫時,調度系統會調用一個以前註冊過的函數——一般被叫作異步,事件驅動或是基於回調的編程。併發
在這種模型下,以前發送電子郵件的函數應該是象這樣的:
- 調用一個鏈接函數,用以鏈接到遠程服務器;
- 鏈接函數馬上返回,暗示着當鏈接創建後,將調用電子郵件發送的通知;而且
- 鏈接一旦創建,系統就會通知發送電子郵件的函數,鏈接已經準備就緒。
於咱們最初阻塞的步驟相比,上面這種非阻塞的步驟有什麼好處呢?當發送電子郵件的函數在鏈接創建以前沒法繼續的時候,程序的其餘部分依然能夠執行其餘任務,好比說開始爲其餘電子郵件的鏈接執行相似的步驟。因而,整個程序就不會卡在等待一個鏈接的創建上。
callback
callback(回調)是通知應用程序數據已經就緒的經典模型。應用程序在調用一個方法,試圖獲取一些數據時,同時提供一個callback函數,當數據就緒時,這個 callback函數會被調用,而且就緒的數據就是調用的參數之一。所以,應用程序應該在callback函數中,繼續執行以前獲取這些數據時,想要執行的任務(當時沒能立刻獲得這些數據,因此當時沒法執行這些任務——譯者注)。
在同步編程中,一個函數會先請求數據,而後等待數據,最後處理它。在異步編程中,一個函數請求數據以後,會在數據準備就緒後,讓外部庫調用其callback函數。
Deferred
Twisted使用Deferred
對象來管理callback序列。做爲Twisted庫的「客戶端」,應用程序將一連串函數添加到Deferred對象中,當異步請求的結果準備就緒時,這一連串函數將被按順序調用(這一連串函數被稱爲一個callback序列,或是一條callback鏈),一塊兒添加的還有另一連串函數,當異步請求出現錯誤的時候,他們將被調用(稱做一個errback序列,或是一條errback鏈)。異步庫代碼會在結果準備就緒時,調用第一個callback,或是在出現錯誤時,調用第一個errback,而後Deferred
對象就會將callback或errback的返回結果傳遞給鏈中的下一個函數。
Deferred解決的問題
Deferred被設計用來幫助解決第二類併發問題——非計算集中型任務,而且延遲時間是能夠估計的。等待硬盤訪問,數據庫訪問和網絡訪問的函數都屬於這一類,儘管時間延遲不盡相同。
Deferred被設計用來使Twisted程序能夠無阻塞地等待數據,直到數據準備就緒。爲了達到這個目的,Deferred爲庫與應用程序提供了一個簡單的callback管理接口。庫能夠經過調用Deferred.callback
來把準備就緒的數據傳回給應用程序,或者調用Deferred.errback
來報告一個錯誤。應用程序能夠按它們但願的順序,設置結果處理邏輯,以callback與errback的形式添加到Deferred對象中去。
使 CPU儘量的有效率,是Deferred與該問題的其餘解決方案背後的基本思路。若是一個任務在等待數據,與其讓CPU(以及程序自己!)眼巴巴地等着數據(對於進程,這一般叫作「阻塞」),不如讓程序同時執行些別的操做,而且同時留意着某些信號——一旦數據準備就緒,程序就能夠(但不是當即——譯者注)回到剛纔的地方繼續執行。
在Twisted裏,一個函數返回一個Deferred對象,意味着它給調用者一個信號:它在等待數據。當數據準備就緒後,程序就會激活那個Deferred對象的callback鏈,來處理數據。
Deferred——數據即將到來的信號
在以前咱們發送電子郵件的例子中,父函數調用了一個鏈接遠程服務器的函數。異步性要求這個鏈接函數必須不等待結果而直接返回,父函數才能夠作其餘事情。但是,父函數或者它的控制程序又是怎麼知道鏈接還沒有創建的呢?當鏈接創建後,它又是怎麼使用鏈接的呢?
Twisted有一個對象能夠做爲這種狀況的一個信號。鏈接函數返回一個twisted.internet.defer.Deferred
對象,給出一個操做還沒有完成的信號。
Deferred有兩個目的。第一,它說「我是一個信號,只是通知你無論你剛纔要我作的什麼,結果尚未出來」。第二,你可讓Deferred在結果出來後執行你的東西。
callback
添加一個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('http://twistedmatrix.com/') //一旦調用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('http://twistedmatrix.com/')
# 向Deferred中添加兩個callback————讓Deferred在頁面內容下載完以後執行
# lowerCaseContents,而後將其返回結果做爲參數,調用printContents
deferred.addCallback(lowerCaseContents) //當有多個addCallback的時候,第一個addCallback(lowerCaseContents)執行後的結果,會看成第二個addCallback(printContents)
函數的參數
deferred.addCallback(printContents)
reactor.run()
錯誤處理:errback
正如異步函數會在其結果產生以前返回,在有可能檢測到錯誤以前返回也是能夠的:失敗的鏈接,錯誤的數據,協議錯誤,等等。正如你能夠將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('http://twistedmatrix.com/does-not-exist')
# 向Deferred添加callback,以處理頁面內容
deferred.addCallback(printContents)
# 向Deferred添加errback,以處理任何錯誤
deferred.addErrback(errorHandler)
reactor.run()
結論
在這篇文檔中,你應該:
- 認識了爲何複雜網絡程序須要某種形式的併發性;
- 瞭解了Twisted框架用異步調用的形式支持併發;
- 瞭解了Twisted框架使用Deferred對象來管理callback鏈;
- 認識了
getPage
函數如何返回一個Deferred對象; - 向那個Deferred對象添加了callback與errback;以及
- 認識了Deferred的callback鏈與errback鏈的觸發。
參考資料
由於Deferred的抽象是Twisted編程中如此核心的一部分,以致於另外還有幾篇關於它的詳細的指南:
- 使用Deferred,一篇關於使用Deferred的更完整的指南,包括鏈式Deferred。
- 產生Deferred,一篇關於建立Deferred和觸發他們callback鏈的指南。