在上一個部分咱們對比了Twisted與 Erlang,並將注意力集中在它們共有的一些思想上.結果代表使用Erlang也是很是簡便的,由於異步I/O和反應式編程是Erlang運行時和進程模型的關鍵元素.python
今天咱們想走得更遠一點,去看一看 Haskell —— 另外一種功能性語言,然而與Erlang有很大不一樣(固然與Python也不一樣).這裏面沒有太多的平行概念,但咱們仍然會發現藏在下面的異步I/O概念.react
雖然Erlang是功能性語言,它主要關注可靠的併發模型.Haskell,另外一方面,是徹頭徹尾功能性的,它無恥地利用了範疇論的概念,如 函子 和 單子.git
不要慌.咱們這裏不會涉及那些複雜的東西(雖然咱們能夠).相反,咱們將關注一個Haskell的更加傳統的功能性特性:惰性. 像許多功能性語言同樣(除了Erlang), Haskell支持 惰性計算. 在懶惰計算語言中,程序的文字並不過多的描述怎樣計算須要計算的東西.具體實施計算的細節通常留給了編譯器和運行時系統.程序員
同時,須要進一步指出,做爲惰性計算推動的運行時可能一次只計算表達式的一部分(惰性的)而不是所有.通常地,運行時只提供維持當前計算繼續所需的最小計算量.github
這裏有一個使用Haskell head 語句的簡單例子,這是一個提取列表第一個元素的函數,對於列表[1,2,3](Haskell與Python分享一些列表句法):編程
head [1,2,3]
若是你安裝了GHC Haskell運行時,你能夠本身試一試:服務器
[~] ghci GHCi, version 6.12.1: http://www.haskell.org/ghc/ : ? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Prelude> head [1,2,3] 1 Prelude>
結果是 1, 正如所料.網絡
Haskell列表的句法包含從前幾個元素定義列表的使用功能.例如,列表[2,4,..]是從2開始的偶數序列.到哪結束呢?實際並不結 束.Haskell列表[2,4,..]和其餘如此表述的都是(概念上)無限列表.你能夠在交互式Haskell提示符下計算它,這將試圖打印這個表達式 的結果以下:數據結構
Prelude> [2,4 ..] [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146, ...
你不得不按 Ctrl-C 終止計算由於它本身不會停下來.但因爲是惰性計算,在Haskell中應用無限列表是沒有問題的:多線程
Prelude> head [2,4 ..] 2 Prelude> head (tail [2,4 ..]) 4 Prelude> head (tail (tail [2,4 ..])) 6
這裏咱們分別獲取無限列表的第1、2、三個元素,沒看到任何無限循環.這就是惰性計算的本質.Haskell運行時只構造完成 head 函數所需的列表,而不是先構造整個列表(這將致使無限循環),再將整個列表傳遞給 head.這個列表的其他部分跟本沒有被構造,由於它們對繼續推動計算毫無心義.
當咱們引入 tail 函數時,Haskell被迫進一步構造列表,可是又一次僅僅構造了知足下一次計算所需的列表.同時,一旦計算結束,列表(未完成的)被丟棄了.
這裏是一些部分計算無限列表的Haskell代碼:
Prelude> let x = [1..] Prelude> let y = [2,4 ..] Prelude> let z = [3,6 ..] Prelude> head (tail (tail (zip3 x y z))) (3,6,9)
zip 函數將全部列表壓縮在一塊兒,以後抓取尾部的尾部的頭部.又一次,Haskell沒有發生任何問題,僅僅構造了計算所需的列表.咱們能夠將Haskell運行時"消耗"這些無限列表的過程可視化:
雖然咱們將Haskell運行時畫爲一個簡單的循環,它可能被多線程實現(而且極可能若是你使用GHC版本的Haskell).但這幅圖的關鍵點在於它十分像一個 reactor 循環,消耗從網絡套接字傳來的數據片斷.
你能夠把異步I/O和 reactor 模式視爲一種有限形式的惰性計算.異步I/O的格言是:"僅僅推動你所擁有的數據".同時惰性計算的格言是:"僅僅推動你所需的數據".進一步,一個惰性計算語言在任何地方都使用這個格言,並不只僅是有限範圍的I/O.
但關鍵點在於,對於惰性計算語言,作異步I/O沒什麼大不了的. 編譯器和運行時已經被設計爲一點一點地處理數據結構,於是惰性地處理到來的I/O數據流是標準問題. 如此Haskell運行時,就像Erlang運行時,簡單地集成異步I/O爲套接字抽象的一部分. 咱們以實現一個Haskell詩歌客戶端來展現這個概念.
咱們第一個Haskell詩歌客戶端位於 haskell-client-1/get-poetry.hs. 同Erlang同樣,咱們直接給出了完成版的客戶端,若是你但願學習更多,咱們指出進一步閱讀的參考.
Haskell一樣支持輕量級線程或進程,儘管它們不是Haskell的核心,咱們的Haskell客戶端爲每首須要下載的詩歌建立一個進程.關鍵函數是 runTask,它鏈接到一個套接字而且以輕量級線程啓動 getPoetry 函數.
在這個代碼中,你將看到許多類型定義. Haskell,不像Python和Erlang,是靜態類型的.咱們沒有爲每一個變量定義類型由於Haskell能夠自動地推斷沒有顯示定義的變量(或者 報告錯誤若是不能推斷).許多函數包含IO類型(技術上叫單子)由於Haskell要求咱們將有反作用的代碼從純函數中乾淨地分離(如,執行I/O的代 碼).
getPoetry 函數包含以下行:
poem <- hGetContents h
看起來像從句柄一次讀入整首詩(如TCP套接字).可是Haskell,像往常同樣,是惰性的.Haskell運行時包含一個或更多實際線程,它們在一個選擇循環中執行異步I/O,如此便保存了惰性處理I/O流的可能性.
python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt python blocking-server/slowpoetry.py --port 10002 poetry/science.txt python blocking-server/slowpoetry.py --port 10003 poetry/ecstasy.txt --num-bytes 30
如今編譯Haskell客戶端:
cd haskell-client-1/ ghc --make get-poetry.hs
這將建立一個二進制 get-poetry.最後,針對咱們的服務器運行客戶端:
/get-poetry 10001 10002 1000
你將看到以下輸出:
Task 3: got 12 bytes of poetry from localhost:10003 Task 3: got 1 bytes of poetry from localhost:10003 Task 3: got 30 bytes of poetry from localhost:10003 Task 2: got 20 bytes of poetry from localhost:10002 Task 3: got 44 bytes of poetry from localhost:10003 Task 2: got 1 bytes of poetry from localhost:10002 Task 3: got 29 bytes of poetry from localhost:10003 Task 1: got 36 bytes of poetry from localhost:10001 Task 1: got 1 bytes of poetry from localhost:10001 ...
輸出與前一個異步客戶端有點不一樣,由於咱們只打印一行而不是任意塊的數據.但,你能夠清楚地看到,客戶端是從全部服務器一塊兒處理數據,而不是一個接一個.你一樣能夠注意到客戶端當即打印第一首完成的詩,不等其餘還在繼續處理的詩.
好了,讓咱們清除還剩下的一點討厭東西而且發佈一個僅僅抓取詩歌而不介意任務序號的新版本.它位於 haskell-client-2/get-poetry.hs. 注意它短多了,對於每一個服務器,僅僅鏈接到套接字,抓取全部數據,以後將其發送回去.
OK,讓咱們編譯新的客戶端:
cd haskell-client-2/ ghc --make get-poetry.hs
針對相同的詩歌服務器組運行它:
./get-poetry 10001 10002 10003
最終,你將看到屏幕上出現每首詩的文字.
你將注意到每一個服務器同時向客戶端發送數據.更重要的,客戶端以最快速度打印出第一首詩的每一行,而不去等待其他的詩,甚至當它正在處理其它兩首詩.以後它快速地打印出以前積累的第二首詩.
同時這全部發生的一切都不須要咱們作什麼.這裏沒有回調,沒有傳來傳去的消息,僅僅是一個關於咱們但願程序作什麼的簡潔地描述,並且不多須要告訴它應該怎樣作.其他的事情都是由Haskell編譯器和運行時處理的.漂亮!
從Twisted到Erlang以後到Haskell,咱們能夠看到一個平行的移動,從前景到背景逐步深刻異步編程背後的思想.在Twisted 中,異步編程是其存在的核心激勵理念. Twisted實現做爲一個與Python分離的框架(Python缺少核心的異步抽象如輕量級線程),將異步模型置於首位與核心,當你用Twisted 寫程序時.
在Erlang中,異步對於程序員仍然是可見的,但細節成爲語言材料的一部分和運行時系統,造成一個抽象使得異步消息在同步進程之間交換.
最後,在Haskell中,異步I/O僅僅是運行時中的另外一個技術,大部分對於程序員是不可見的,由於提供惰性計算是Haskell的中心理念.
對於以上狀況,咱們尚未介紹任何深邃的思想.咱們僅僅指出許多而且有趣的異步模型出現的地方,這種模型能夠被多種方式表達.
如今到告終束探索Twisted以外異步系統的時刻,而且完成了本系列的倒數第二部分. 在":doc:`p22`"中,咱們將作一個總結,以及建議一些學習Twisted的方法.