I/O 模型及其設計模式


 

來源:伯樂在線 - 咸菜

鏈接:http://blog.jobbole.com/104638/

  

前言

 

I/O在軟件開發中的重要性無需多言,無論是在操作系統、網絡協議、DBMS這種底層支撐軟件還是在移動APP,大型網站服務器等應用軟件的開發中都是最核心最重要的部分。特別是現在軟件服務使用量和數據量爆炸增長的時代,大數據背景下的高可用分布式系統都離不開高效穩定的I/O。本文就簡要分析各類I/O模型的演進、基本原理、應用方法、優缺點及其使用場景。文章最后會簡要分析兩種常用的I/O設計模式。需要說明的是,對於I/O中的許多問題是沒有統一、確切的答案的,因此在分析一些問題的時候會根據自己的理解來說明,很有可能和其他書籍或者文章的觀點有出入,至於哪種理解更好歡迎交流。

 

文章大綱:

 

1. 阻塞/非阻塞 同步/異步

 

2. I/O中的阻塞/非阻塞 同步/異步

 

3. BIO、偽異步I/O、NIO、AIO四種常用I/O模型及其對比

 

4. Reactor、Proactor兩種I/O設計模式及其對比

 

5. 總結

 

使用各種I/O模型實現的時間服務器源代碼僅供參考:https://git.oschina.net/wangxu/TimeServer

 

參考《Neety權威指南》

 阻塞/非阻塞 & 同步/異步

 

在介紹I/O模型之前需要先理解幾個概念,理解了阻塞/非阻塞 & 同步/異步的聯系和區別才能理解I/O模型。關於阻塞/非阻塞 & 同步/異步有很多資料的說法不一致,但是也沒必要咬文嚼字,只要能夠結合實際的例子來理解每種模型的基本原理就可以了。下面就我自己的理解來說一下這個問題。

 

阻塞/非阻塞:首先需要知道阻塞/非阻塞是針對某一個事件(線程/進程)來說的。對於阻塞,如果一個事件在觸發一個請求后,由於條件不滿足,那么這個事件就會停在這個請求上。拿一個線程來說,一個線程在請求了一個系統調用之后,由於當前不滿足執行這個請求的條件,那么這個線程就會停在這個請求上,直到請求執行完畢或者出現異常返回,這個線程阻塞的時候操作系統不會分配CPU時間。對於非阻塞,如果一個事件在觸發一個請求后,無論當前是否滿足執行請求的條件,都會把結果或者異常返回給請求事件,這個事件不會被阻塞。

 

舉個栗子:你想去ATM機取錢,但是前面有人排隊,那么這時候你必須要排在后面。在輪到你取錢之前你哪也不能去什么也不能干,只能排隊等待。那這就是阻塞式的。那如果你閑ATM人太多,你去了銀行大廳到櫃台取錢。那么你到了之后,大堂經理會提示你去一張排隊號,在你取完排隊號之后,你不必非要站在櫃台前面等着,你可以坐着玩手機,如果時間夠的話你可以上個廁所,吃個飯都是可以的。如果輪到你了,叫號系統就會廣播:請xxx號顧客到xxx號窗口辦理業務。這時候你聽見就可以去取錢了。這就是非阻塞式的。

 

同步/異步:首先需要強調一點,同步/異步是針對多個事件(線程/進程)來說的。拿單個的事件來談同步/異步是沒有意義的。有點類似於操作系統中進程調度中狹義的同步(和資源互斥相對)。如果事件A需要等待事件B的完成才能完成,這種就可以說是同步的。如果事件A的完成需要事件B的執行結果,但是在B完成之前A不會因為B沒有完成而等待,而是繼續執行,等待B完成之后自動補全A的任務。類似這種的就是異步的。

 

舉個栗子:還是取錢的例子,如果你像上面說的那兩種方法取錢的話,你還是得自己出馬,排隊/取號,辦理取錢業務然后回家,這種都是同步的。但是如果你辦了一張銀行的VIP金卡,那好了,給銀行打個電話說需要多少錢什么時候需要,然后你可以接着干你的事情,就當這件事情不存在一樣。銀行的業務員會把你需要的錢自動在合適的時間給你送來。那么萬一業務員在路上被搶劫了,銀行也會給你打電話通知你。像這種就類似異步的操作。

 

區分阻塞/同步和非阻塞/異步:只要理解了阻塞/非阻塞式針對單一事件,同步/異步是針對多個事件這個核心就能夠區分阻塞/同步和非阻塞/異步這兩組完全不同的概念。

 

I/O中的阻塞/非阻塞 & 同步/異步

 

理解了這幾種不同的概念,下面來具體的看一下這幾種概念組合起來在I/O中的應用。

 

同步阻塞I/O:

最常用的一個模型是同步阻塞 I/O 模型。在這個模型中,用戶空間的應用程序執行一個系統調用,這會導致應用程序阻塞。這意味着應用程序會一直阻塞,直到系統調用完成為止(數據傳輸完成或發生錯誤)。調用應用程序處於一種不再消費 CPU 而只是簡單等待響應的狀態,因此從處理的角度來看,這是非常有效的。在調用 read 系統調用時,應用程序會阻塞並對內核進行上下文切換。然后會觸發讀操作,當響應返回時(從我們正在從中讀取的設備中返回),數據就被移動到用戶空間的緩沖區中。然后應用程序就會解除阻塞(read 調用返回)。從應用程序的角度來說,read 調用會延續很長時間。實際上,在內核執行讀操作和其他工作時,應用程序的確會被阻塞。

 

 

 

                                                  阻塞I/O模型

 

同步非阻塞I/O:

在這種模型中,設備是以非阻塞的形式打開的。這意味着 I/O 操作不會立即完成,read 操作可能會返回一個錯誤代碼,說明read請求不能立即滿足。需要應用程序調用許多次來等待操作完成。這可能效率不高,因為在很多情況下,當內核執行這個命令時,應用程序必須要進行忙碌等待,直到數據可用為止,或者試圖執行其他工作。這個方法可以引入 I/O 操作的延時,因為數據在內核中變為可用到用戶調用read 返回數據之間存在一定的間隔,這會導致整體數據吞吐量的降低。

 

 

                                             非阻塞I/O模型

 

異步阻塞I/O:

個人覺得談這種模型的意義不大,以為請求線程已經阻塞,那么異步的作用就不大了。但還有一種理解就是這里的阻塞是通知的阻塞,而不是請求線程的阻塞,也就是一種帶有阻塞通知的非阻塞 I/O。在這種模型中,配置的是非阻塞 I/O,然后使用阻塞select 系統調用來確定一個 I/O 描述符何時有操作。使 select 調用非常有趣的是它可以用來為多個描述符提供通知,而不僅僅為一個描述符提供通知。對於每個提示符來說,我們可以請求這個描述符可以寫數據、有讀數據可用以及是否發生錯誤的通知。

 

 

                                                            復用I/O模型

 

異步非阻塞I/O:

異步非阻塞 I/O 模型是一種處理與 I/O 重疊進行的模型。讀請求會立即返回,說明 read 請求已經成功發起了。在后台完成讀操作時,應用程序然后會執行其他處理操作。當 read 的響應到達時,就會產生一個信號或執行一個基於線程的回調函數來完成這次 I/O 處理過程。在一個進程中為了執行多個 I/O 請求而對計算操作和 I/O 處理進行重疊處理的能力利用了處理速度與 I/O 速度之間的差異。當一個或多個 I/O 請求掛起時,CPU 可以執行其他任務;或者更為常見的是,在發起其他 I/O 的同時對已經完成的 I/O 進行操作。

 

 

                                                                  異步I/O模型

 

BIO、偽異步I/O、NIO、AIO四種常用I/O模型及其對比

 

BIO/偽異步IO:BIO是阻塞(block)I/O同時也是同步的,就是說BIO是一種同步阻塞I/O模型,在基於傳統的同步阻塞I/O模型的開發中,需要服務端監聽端口號,然后客戶端通過IP和端口號來和服務端建立TCP連接,以同步阻塞的方法進行數據傳輸。一般使用BIO的服務端設計中是一客戶一線程模型的。

 

 

BIO模型

 

這種模型是有問題的,如果連接客戶端較多會大大消耗服務端的資源,如果線程數量超過服務端能承受的最大數量,那么服務器可能會出現很嚴重的后果。所以出現了偽異步I/O模型,偽異步I/O模型對BIO模型進行了改進,采用了線程池來處理客戶連接線程。這樣就可以靈活的設置線程池的大小,可以避免服務端資源耗盡的問題。

 

 

偽異步I/O模型

 

但是偽異步I/O模型並沒有從根本上解決客戶連接線程的阻塞問題,只是對BIO模型進行了簡單的優化。面對巨大的連接客戶線程還是存在客戶線程阻塞時間較長反應較慢的問題。

 

NIO:NIO是一種非阻塞(non-block)的,同時又是同步的I/O模型。這里只談一種帶有Selector多路復用器的NIO。NIO是使用一個Selector復用器線程來輪詢每一個客戶端連接,這樣就不用阻塞用戶線程,同時也不用每個用戶線程忙等待。只使用一個線程來輪詢I/O事件,這樣一來就可以從根本上解決用戶線程阻塞的問題。所以NIO模型比較適合高負載、高並發的網絡應用。能夠充分利用系統資源快速處理請求返回響應消息。NIO適合連接數較多連接時間I/O任務較短的場景,例如即時消息服務器。如果連接數不多而且比較固定,I/O任務較長使用NIO模型就會得不償失,不僅增加了編程的復雜度而且達不到預期的效果。

 

AIO:AIO是一種異步(Asyncronous)非阻塞的I/O模型,它需要操作系統內核線程的支持,一個用戶線程發起一個系統調用請求后就可以繼續執行,內核線程執行完系統調用會根據回調函數來完成處理工作。所以AIO模型應該是一種比較理想的模型,因為操作系統內核線程做了一些工作,所以在編程復雜度上AIO要比NIO要簡單一些。AIO比較適合連接數較多其I/O任務比較長的場景。

 

借助《Netty權威指南》上的一張表對比一下各個I/O模型的特點:

 

 

 

Reactor、Proactor兩種I/O設計模式及其對比

 

Reactor:Reactor模式是基於NIO多路復用I/O模型實現的一種常用的模式。在Reactor模式中每個客戶連接會注冊自己感興趣的事件,然后Selector多路復用器會輪詢每個就緒事件,每到一個事件執行一個事件。Reactor實現了一個被動的事件分離和分發模型,服務等待請求事件的到來,再通過不受間斷的同步處理事件,從而做出反應。Reactor比較適合連接數較多但是任務量較小的場景。Reactor實現相對簡單,對於耗時短的處理場景處理高效;操作系統可以在多個事件源上等待,並且避免了多線程編程相關的性能開銷和編程復雜性;事件的串行化對應用是透明的,可以順序的同步執行而不需要加鎖;事務分離,將與應用無關的多路分解和分配機制和與應用相關的回調函數分離開來。Reactor同時接收多個服務請求,並且依次同步的處理它們的事件驅動程序;但是Reactor不適合執行耗時較長的操作,處理耗時長的操作會造成事件分發的阻塞,影響到后續事件的處理。

 

 



Proactor:Proactor模式是基於AIO實現的一種高效的I/O設計模式。在Proactor中用戶連接請求I/O操作,這時操作系統內核就會調用相應的系統調用來完成請求,內核線程在完成用戶的I/O請求后把執行結果放在完成事件隊列中,Proactor從完成事件隊列中取出結果根據相應的回調處理器來完成對操作結果的相應處理。Proactor實現了一個主動的事件分離和分發模型;這種設計允許多個任務並發的執行,從而提高吞吐量;並可執行耗時長的任務(各個任務間互不影響)。相對Reactor來說,Proactor性能更高,能夠處理耗時長的並發場景;可以異步接收和同時處理多個服務請求的事件驅動程序;但是Proactor依賴操作系統對異步操作的支持,各類操作系統對異步I/O支持的實現細節有差異,沒有形成統一的標准。


 

 

總結

 

沒有最好的I/O模型,只有最適合的I/O模型。

 


免責聲明!

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



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