两种IO模式:Proactor与Reactor模式
在高性能的I/O設(shè)計中,有兩個比較著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor運用于異步I/O操作。
?
????? ?在比較這兩個模式之前,我們首先的搞明白幾個概念,什么是阻塞和非阻塞,什么是同步和異步
?同步和異步是針對應(yīng)用程序和內(nèi)核的交互而言的,同步指的是用戶進(jìn)程觸發(fā)IO操作并等待或者輪詢的去查看IO操作是否就緒,而異步是指用戶進(jìn)程觸發(fā)IO操作以后便開始做自己的事情,而當(dāng)IO操作已經(jīng)完成的時候會得到IO完成的通知(異步的特點就是通知)。
而阻塞和非阻塞是針對于進(jìn)程在訪問數(shù)據(jù)的時候,根據(jù)IO操作的就緒狀態(tài)來采取的不同方式,說白了是一種讀取或者寫入操作函數(shù)的實現(xiàn)方式,阻塞方式下讀取或者寫入函數(shù)將一直等待,而非阻塞方式下,讀取或者寫入函數(shù)會立即返回一個狀態(tài)值。??
一般來說I/O模型可以分為:同步阻塞,同步非阻塞,異步阻塞,異步非阻塞IO
??
???同步阻塞IO:?
???在此種方式下,用戶進(jìn)程在發(fā)起一個IO操作以后,必須等待IO操作的完成,只有當(dāng)真正完成了IO操作以后,用戶進(jìn)程才能運行。JAVA傳統(tǒng)的IO模型屬于此種方式!
?
???同步非阻塞IO:?
在此種方式下,用戶進(jìn)程發(fā)起一個IO操作以后邊可返回做其它事情,但是用戶進(jìn)程需要時不時的詢問IO操作是否就緒,這就要求用戶進(jìn)程不停的去詢問,從而引入不必要的CPU資源浪費。其中目前JAVA的NIO就屬于同步非阻塞IO。
?
???異步阻塞IO:
???此種方式下是指應(yīng)用發(fā)起一個IO操作以后,不等待內(nèi)核IO操作的完成,等內(nèi)核完成IO操作以后會通知應(yīng)用程序,這其實就是同步和異步最關(guān)鍵的區(qū)別,同步必須等待或者主動的去詢問IO是否完成,那么為什么說是阻塞的呢?因為此時是通過select系統(tǒng)調(diào)用來完成的,而select函數(shù)本身的實現(xiàn)方式是阻塞的,而采用select函數(shù)有個好處就是它可以同時監(jiān)聽多個文件句柄(如果從UNP的角度看,select屬于同步操作。因為select之后,進(jìn)程還需要讀寫數(shù)據(jù)),從而提高系統(tǒng)的并發(fā)性!
?
???異步非阻塞IO:
???在此種模式下,用戶進(jìn)程只需要發(fā)起一個IO操作然后立即返回,等IO操作真正的完成以后,應(yīng)用程序會得到IO操作完成的通知,此時用戶進(jìn)程只需要對數(shù)據(jù)進(jìn)行處理就好了,不需要進(jìn)行實際的IO讀寫操作,因為真正的IO讀取或者寫入操作已經(jīng)由內(nèi)核完成了。目前Java中還沒有支持此種IO模型。???
???????? 搞清楚了以上概念以后,我們再回過頭來看看,Reactor模式和Proactor模式。
?
?(其實阻塞與非阻塞都可以理解為同步范疇下才有的概念,對于異步,就不會再去分阻塞非阻塞。對于用戶進(jìn)程,接到異步通知后,就直接操作進(jìn)程用戶態(tài)空間里的數(shù)據(jù)好了。)
?
首先來看看Reactor模式,Reactor模式應(yīng)用于同步I/O的場景。我們分別以讀操作和寫操作為例來看看Reactor中的具體步驟:
讀取操作:
1.?應(yīng)用程序注冊讀就緒事件和相關(guān)聯(lián)的事件處理器
2.?事件分離器等待事件的發(fā)生
3.?當(dāng)發(fā)生讀就緒事件的時候,事件分離器調(diào)用第一步注冊的事件處理器
4.?事件處理器首先執(zhí)行實際的讀取操作,然后根據(jù)讀取到的內(nèi)容進(jìn)行進(jìn)一步的處理
寫入操作類似于讀取操作,只不過第一步注冊的是寫就緒事件。
?
?
下面我們來看看Proactor模式中讀取操作和寫入操作的過程:
讀取操作:
1.?應(yīng)用程序初始化一個異步讀取操作,然后注冊相應(yīng)的事件處理器,此時事件處理器不關(guān)注讀取就緒事件,而是關(guān)注讀取完成事件,這是區(qū)別于Reactor的關(guān)鍵。
2.?事件分離器等待讀取操作完成事件
3.?在事件分離器等待讀取操作完成的時候,操作系統(tǒng)調(diào)用內(nèi)核線程完成讀取操作(異步IO都是操作系統(tǒng)負(fù)責(zé)將數(shù)據(jù)讀寫到應(yīng)用傳遞進(jìn)來的緩沖區(qū)供應(yīng)用程序操作,操作系統(tǒng)扮演了重要角色),并將讀取的內(nèi)容放入用戶傳遞過來的緩存區(qū)中。這也是區(qū)別于Reactor的一點,Proactor中,應(yīng)用程序需要傳遞緩存區(qū)。
4.?事件分離器捕獲到讀取完成事件后,激活應(yīng)用程序注冊的事件處理器,事件處理器直接從緩存區(qū)讀取數(shù)據(jù),而不需要進(jìn)行實際的讀取操作。
Proactor中寫入操作和讀取操作,只不過感興趣的事件是寫入完成事件。
?
從上面可以看出,Reactor和Proactor模式的主要區(qū)別就是真正的讀取和寫入操作是有誰來完成的,Reactor中需要應(yīng)用程序自己讀取或者寫入數(shù)據(jù),而Proactor模式中,應(yīng)用程序不需要進(jìn)行實際的讀寫過程,它只需要從緩存區(qū)讀取或者寫入即可,操作系統(tǒng)會讀取緩存區(qū)或者寫入緩存區(qū)到真正的IO設(shè)備.
?
????????? 綜上所述,同步和異步是相對于應(yīng)用和內(nèi)核的交互方式而言的,同步 需要主動去詢問,而異步的時候內(nèi)核在IO事件發(fā)生的時候通知應(yīng)用程序,而阻塞和非阻塞僅僅是系統(tǒng)在調(diào)用系統(tǒng)調(diào)用的時候函數(shù)的實現(xiàn)方式而已。
//
?
說到阻塞,首先得說說I/O等待。I/O等待是不可避免的,那么既然有了等待,就會有阻塞,但是注意,我們說的阻塞是指當(dāng)前發(fā)起I/O操作的進(jìn)程被阻塞
同步阻塞I/O便是指,當(dāng)進(jìn)程調(diào)用某些涉及I/O操作的系統(tǒng)調(diào)用或庫函數(shù)時,比如accept()(注意accept也算在了i/o操作)、send()、recv()等,進(jìn)程便暫停下來,等待I/O操作完成再繼續(xù)運行。這是一種簡單而有
效的I/O模型,它可以和多進(jìn)程結(jié)合起來有效的利用CPU資源,但是代價就是多進(jìn)程的大量內(nèi)存開銷。
?
?
同步阻塞 進(jìn)程坐水,就不能燒粥?
同步非阻塞? 類似于用一個進(jìn)程坐水,燒粥. while(true){if... if... }? 好處就是一個進(jìn)程處理多個i/o請求. 劣勢就是需要不停的輪詢.
區(qū)別在于等不等待數(shù)據(jù)就緒. 因為數(shù)據(jù)占了等待的80%時間. 同步非阻塞的優(yōu)勢就是一個進(jìn)程里同時處理多個I/O操作。
?
在同步阻塞I/O中,進(jìn)程實際上等待的時間可能包括兩部分,一個是等待數(shù)據(jù)的就緒,另一個是等待數(shù)
據(jù)的復(fù)制,對于網(wǎng)絡(luò)I/O來說,前者的時間可能要更長一些。
與此不同的是,同步非阻塞I/O的調(diào)用不會等待數(shù)據(jù)的就緒,如果數(shù)據(jù)不可讀或者不可寫,它會立即返
回告訴進(jìn)程。
比如我們使用非阻塞recv()接收網(wǎng)絡(luò)數(shù)據(jù)的時候,如果網(wǎng)卡緩沖區(qū)中沒有可接收的數(shù)據(jù),函數(shù)就及時返回,告訴進(jìn)程沒有數(shù)據(jù)可讀了。相比于阻塞I/O,這種非阻塞I/O結(jié)合反復(fù)的輪詢來嘗試
數(shù)據(jù)是否就緒,防止進(jìn)程被阻塞,最大的好處便在于可以在一個進(jìn)程里同時處理多個I/O操作。但正是由于需要進(jìn)程執(zhí)行多次的輪詢來查看數(shù)據(jù)是否就緒,這花費了大量的CPU時間,使得進(jìn)程處于忙碌等待狀態(tài)。
非阻塞I/O一般只針對網(wǎng)絡(luò)I/O有效,我們只要在socket的選項設(shè)置中使用O_NONBLOCK即可,這樣對于該socket的send()或recv()便采用非阻塞方式。
如果服務(wù)器想要同時接收多個TCP連接的數(shù)據(jù),就必須輪流對每個socket調(diào)用接收數(shù)據(jù)的方法,比如recv()。不管這些socket有沒有可以接收的數(shù)據(jù),都要詢問一遍,假如大部分socket并沒有數(shù)據(jù)可以接收,那么進(jìn)程便會浪費很多CPU時間用于檢查這些socket,這顯然不是我們所希望看到的。
同步和異步,阻塞和非阻塞,有些混用,其實它們完全不是一回事,而且它們修飾的對象也不相同。
阻塞和非阻塞是指當(dāng)進(jìn)程訪問的數(shù)據(jù)如果尚未就緒,進(jìn)程是否需要等待,簡單說這相當(dāng)于函數(shù)內(nèi)部的實現(xiàn)區(qū)別,也就是未就緒時是直接返回還是等待就緒;
而同步和異步是指訪問數(shù)據(jù)的機(jī)制,同步一般指主動請求并等待I/O操作完畢的方式,當(dāng)數(shù)據(jù)就緒后在讀寫的時候必須阻塞(區(qū)別就緒與讀寫二個階段,同步的讀寫必須阻塞),異步則指主動請求數(shù)據(jù)后便可以繼續(xù)處理其它任務(wù),隨后等待I/O,操作完畢的通知,這可以使進(jìn)程在數(shù)據(jù)讀寫時也不阻塞。(等待"通知")
?
多數(shù)情況下,Web服務(wù)器對這些請求采用基于隊列的自由競爭,通過多執(zhí)行流(多進(jìn)程或多線程)來充分占 用CPU以及I/O資源,減少任何無辜的等待時間,這其中包括了很多種具體實現(xiàn)的并發(fā)策略,
在實際應(yīng)用中,特別是Web服務(wù)器,同時處理大量的文件描述符是必不可少的.多路I/O就緒通知的出現(xiàn),提供了對大量文件描述符就緒檢查的高性能方案,它允許進(jìn)程(比如電子屏,會聞到各個飯館做好飯菜的味道)通過一種方法來同時監(jiān)視所有文件描述符,并可以快速獲得所有就緒的文件描述符,然后只針對這些文件描述符進(jìn)行數(shù)據(jù)訪問。
回到買面條的故事中,假如你不止買了一份面條,還在其它幾個小吃店買了餃子、粥、餡餅等,因為一起逛街的朋友看到你的面條后也餓了。這些東西都需要時間來等待制作。在同步非阻塞I/O模型中,你
要輪流不停的去各個小吃店詢問進(jìn)度,痛苦不堪。現(xiàn)在引入多路I/O就緒通知后,小吃城管理處給大廳安裝了一塊電子屏幕,以后所有小吃店的食物做好后,都會顯示在屏幕上,這可真是個好消息,你只需
要間隔性的看看大屏幕就可以了,也許你還可以同時逛逛附近的商店,在不遠(yuǎn)處也可以看到大屏幕。
?
多路就緒:1.強(qiáng)調(diào)多路. 2.只針對請求數(shù)據(jù)是否就緒.不針對i/o讀寫
epoll針對的是這樣的場景.
select, epoll都只需要進(jìn)程(我)被動接收到數(shù)據(jù)就緒(面條)"通知".符合異步的定義. 不需要一直在飯館等(同步阻塞).或輪詢(同步非阻塞).
總結(jié)
以上是生活随笔為你收集整理的两种IO模式:Proactor与Reactor模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高性能实践IO之Reactor模式
- 下一篇: Java NIO:浅析I/O模型