天天看點

同步,異步與多線程的關系

原作位址:https://github.com/likebeta/twisted-intro-cn/blob/gh-pages/zh/p05.md

前言

最近有人在Twisted郵件清單中提出諸如"為任務緊急的人提供一份Twisted介紹"的需求。值得提前透露的是,這個系列并不會如他們所願。尤其是介紹Twisted架構和基于Python 的異步程式設計而言,可能短時間無法講清楚。是以,如果你時間緊急,這恐怕不是你想找的資料。

我相信如果對異步程式設計模型一無所知,快速的介紹同樣無法讓你對其有所了解,至少你得稍微懂點基礎知識吧。我已經用Twisted架構幾年了,是以思考過我當初是怎麼學習它(學得很慢)并發現學習它的最大難度并不在Twisted本身,而在于對其模型的了解,隻有了解了這個模型,你才能更好去寫和了解異步程式的代碼。大部分Twisted的代碼寫得很清晰,其線上文檔也非常棒(至少在開源軟體這個層次上可以這麼說)。但如果不了解這個模型,不管是讀Twisted源碼還是使用Twisted的代碼更或者是相關文檔,你都會感到非常的傷腦筋。

是以,我會用前面幾個部分來介紹這個模型以讓你掌握它的機制,稍後會介紹一下Twisted的特點。實際上,一開始,我們并不會使用Twisted,相反,會使用簡單的Python代碼來說明一個異步模型是如何工作的。我們在初次學習Twisted的時,會從你平常都不會直接使用的底層的實作講起。Twisted是一個高度抽象的體系,是以在使用它時,你會體會到其多層次性。但當你去學習尤其是嘗試着了解它是如何工作時,這種為抽像而帶來的多層次性會給你帶來極大的了解難度。是以,我們準備來個從内到外,從低層開始學習它。

模型

為了更好的了解異步程式設計模型的特點,我們來回顧一下兩個大家都熟悉的模型。在闡述過程中,我們假設一個包含三個互相獨立任務的程式。在此,除了規定這些任務都要完成自己工作外,我們先不作具體的解釋,後面我們會慢慢具體了解它們。請注意:在此我用"任務"這個詞,這意味着它需要完成一些事情。

第一個模型是單線程的同步模型,如圖1所示:

同步,異步與多線程的關系

圖1 同步模型

這是最簡單的程式設計方式。在一個時刻,隻能有一個任務在執行,并且前一個任務結束後一個任務才能開始。如果任務都能按照事先規定好的順序執行,最後一個任務的完成意味着前面所有的任務都已無任何差錯地完成并輸出其可用的結果—這是多麼簡單的邏輯。 下面我們來呈現第二個模型,如圖2所示:

同步,異步與多線程的關系

圖2 線程模型

在這個模型中,每個任務都在單獨的線程中完成。這些線程都是由作業系統來管理,若在多處理機、多核處理機的系統中可能會互相獨立的運作,若在單處理機上,則會交錯運作。關鍵點在于,線上程模式中,具體哪個任務執行由作業系統來處理。但程式設計人員則隻需簡單地認為:它們的指令流是互相獨立且可以并行執行。雖然,從圖示看起來很簡單,實際上多線程程式設計是很麻煩的,你想啊,任務之間的要通信就要是線程之間的通信。線程間的通信那不是一般的複雜。什麼郵槽、通道、共享記憶體。。。 唉—__-

一些程式用多處理機而不是多線程來實作并行運算。雖然具體的程式設計細節是不同的,但對于我們要研究的模型來說是一樣的。

下面我們來介紹一下異步程式設計模型,如圖3所示

同步,異步與多線程的關系

圖3 異步模型

在這個模型中,任務是交錯完成,值得注意的是:這是在單線程的控制下。這要比多線程模型簡單多了,因為程式設計人員總可以認為隻有一個任務在執行,而其它的在停止狀态。雖然在單處理機系統中,線程也是像圖3那樣交替進行。但作為程式員在使用多線程時,仍然需要使用圖2而不是圖3的來思考問題,以防止程式在挪到多處理機的系統上無法正常運作(考慮到相容性)。但單線程的異步程式不管是在單處理機還是在多處理機上都能很好的運作。

在異步程式設計模型與多線程模型之間還有一個不同:在多線程程式中,對于停止某個線程啟動另外一個線程,其決定權并不在程式員手裡而在作業系統那裡,是以,程式員在編寫程式過程中必須要假設在任何時候一個線程都有可能被停止而啟動另外一個線程。相反,在異步模型中,一個任務要想運作必須顯式放棄目前運作的任務的控制權。這也是相比多線程模型來說,最簡潔的地方。 值得注意的是:将異步程式設計模型與同步模型混合在同一個系統中是可以的。但在介紹中的絕大多數時候,我們隻研究在單個線程中的異步程式設計模型。

動機

我們已經看到異步程式設計模型之是以比多線程模型簡單在于其單指令流與顯式地放棄對任務的控制權而不是被作業系統随機地停止。但是異步模型要比同步模型複雜得多。程式員必須将任務組織成序列來交替的小步完成。是以,若其中一個任務用到另外一個任務的輸出,則依賴的任務(即接收輸出的任務)需要被設計成為要接收系列比特或分片而不是一下全部接收。由于沒有實質上的并行,從我們的圖中可以看出,一個異步程式會花費一個同步程式所需要的時間,可能會由于異步程式的性能問題而花費更長的時間。

是以,就要問了,為什麼還要使用異步模型呢? 在這兒,我們至少有兩個原因。首先,如果有一到兩個任務需要完成面向人的接口,如果交替執行這些任務,系統在保持對使用者響應的同時在背景執行其它的任務。是以,雖然背景的任務可能不會運作的更快,但這樣的系統可能會受歡迎的多。

然而,有一種情況下,異步模型的性能會高于同步模型,有時甚至會非常突出,即在比較短的時間内完成所有的任務。這種情況就是任務被強行等待或阻塞,如圖4所示:

同步,異步與多線程的關系

圖4 同步模型中出現阻塞

在圖4中,灰色的部分代表這段時間某個任務被阻塞。為什麼要阻塞一個任務呢?最直接的原因就是等待I/O的完成:傳輸資料或來自某個外部裝置。一個典型的CPU處理資料的能力是硬碟或網絡的幾個數量級的倍數。是以,一個需要進行大I/O操作的同步程式需要花費大量的時間等待硬碟或網絡将資料準備好。正是由于這個原因,同步程式也被稱作為阻塞程式。

從圖4中可以看出,一個可阻塞的程式,看起來與圖3描述的異步程式有點像。這不是個巧合。異步程式背後的最主要的特點就在于,當出現一個任務像在同步程式一樣出現阻塞時,會讓其它可以執行的任務繼續執行,而不會像同步程式中那樣全部阻塞掉。是以一個異步程式隻有在沒有任務可執行時才會出現"阻塞",這也是為什麼異步程式被稱為非阻塞程式的原因。 任務之間的切換要不是此任務完成,要不就是它被阻塞。由于大量任務可能會被阻塞,異步程式等待的時間少于同步程式而将這些時間用于其它實時工作的處理(如與人打交道的接口),這樣一來,前者的性能必然要高很多。

與同步模型相比,異步模型的優勢在如下情況下會得到發揮:

  1. 有大量的任務,以至于可以認為在一個時刻至少有一個任務要運作
  2. 任務執行大量的I/O操作,這樣同步模型就會在因為任務阻塞而浪費大量的時間
  3. 任務之間互相獨立,以至于任務内部的互動很少。

這些條件大多在CS模式中的網絡比較繁忙的伺服器端出現(如WEB伺服器)。每個任務代表一個用戶端進行接收請求并回複的I/O操作。客戶的請求(相當于讀操作)都是互相獨立的。是以一個網絡服務是異步模型的典型代表,這也是為什麼twisted是第一個也是最棒的網絡庫。

參考

本部分原作參見: dave http://krondo.com/?p=1209

本部分翻譯内容參見楊曉偉的部落格 http://blog.sina.com.cn/s/blog_704b6af70100py9f.html

繼續閱讀