天天看點

高性能I/O設計模式 reactor & proactor

兩種I/O多路複用模式:Reactor和Proactor

 一般地,I/O多路複用機制都依賴于一個事件多路分離器(EventDemultiplexer)。分離器對象可将來自事件源的I/O事件分離出來,并分發到對應的read/write事件處理器(EventHandler)。開發人員預先注冊需要處理的事件及其事件處理器(或回調函數);事件分離器負責将請求事件傳遞給事件處理器。兩個與事件分離器有關的模式是Reactor和Proactor。Reactor模式采用同步IO,而Proactor采用異步IO。

 在Reactor中,事件分離器負責等待檔案描述符或socket為讀寫操作準備就緒,然後将就緒事件傳遞給對應的處理器,最後由處理器負責完成實際的讀寫工作。

 而在Proactor模式中,處理器--或者兼任處理器的事件分離器,隻負責發起異步讀寫操作。IO操作本身由作業系統來完成。傳遞給作業系統的參數需要包括使用者定義的資料緩沖區位址和資料大小,作業系統才能從中得到寫出操作所需資料,或寫入從socket讀到的資料。事件分離器捕獲IO操作完成事件,然後将事件傳遞給對應處理器。比如,在windows上,處理器發起一個異步IO操作,再由事件分離器等待IOCompletion事件。典型的異步模式實作,都建立在作業系統支援異步API的基礎之上,我們将這種實作稱為“系統級”異步或“真”異步,因為應用程式完全依賴作業系統執行真正的IO工作。

 舉個例子,将有助于了解Reactor與Proactor二者的差異,以讀操作為例(類操作類似)。

 在Reactor中實作讀:

 - 注冊讀就緒事件和相應的事件處理器

 - 事件分離器等待事件

 - 事件到來,激活分離器,分離器調用事件對應的處理器。

 - 事件處理器完成實際的讀操作,處理讀到的資料,注冊新的事件,然後返還控制權。

 與如下Proactor(真異步)中的讀過程比較:

 -處理器發起異步讀操作(注意:作業系統必須支援異步IO)。在這種情況下,處理器無視IO就緒事件,它關注的是完成事件。

 - 事件分離器等待操作完成事件

 -在分離器等待過程中,作業系統利用并行的核心線程執行實際的讀操作,并将結果資料存入使用者自定義緩沖區,最後通知事件分離器讀操作完成。

 - 事件分離器呼喚處理器。

 -事件處理器處理使用者自定義緩沖區中的資料,然後啟動一個新的異步操作,并将控制權傳回事件分離器。

實踐現狀 

 由DouglasSchmidt等人開發的開源C++開發架構ACE,提供了大量與平台無關,支援并發的底層類(線程,互斥量等),且在高抽象層次上,提供了兩組不同的類--ACEReactor和ACE Proactor的實作。不過,雖然二者都與平台無關,提供的接口卻各異。

 ACEProactor在windows平台上具有更為優異的性能表現,因為windows在作業系統提供了高效的異步API支援(見http://msdn2.microsoft.com/en-us/library/aa365198.aspx)。

 然而,并非所有的作業系統都在系統級大力支援異步。像很多Unix系統就沒做到。是以,在Unix上,選擇ACEReactor解決方案可能更好。但這樣一來,為了獲得最好的性能,網絡應用的開發人員必須為不同的作業系統維護多份代碼:windows上以ACEProactor為基礎,而Unix系統上則采用ACE Reactor解決方案。

改進方案

 在這部分,我們将嘗試應對為Proactor和Reactor模式建立可移植架構的挑戰。在改進方案中,我們将Reactor原來位于事件處理器内的read/write操作移至分離器(不妨将這個思路稱為“模拟異步”),以此尋求将Reactor多路同步IO轉化為模拟異步IO。以讀操作為例子,改進過程如下:

  - 注冊讀就緒事件及其處理器,并為分離器提供資料緩沖區位址,需要讀取資料量等資訊。

  - 分離器等待事件(如在select()上等待)

  -事件到來,激活分離器。分離器執行一個非阻塞讀操作(它有完成這個操作所需的全部資訊),最後調用對應處理器。

  -事件處理器處理使用者自定義緩沖區的資料,注冊新的事件(當然同樣要給出資料緩沖區位址,需要讀取的資料量等資訊),最後将控制權返還分離器。

 如我們所見,通過對多路IO模式功能結構的改造,可将Reactor轉化為Proactor模式。改造前後,模型實際完成的工作量沒有增加,隻不過參與者間對工作職責稍加調換。沒有工作量的改變,自然不會造成性能的削弱。對如下各步驟的比較,可以證明工作量的恒定:

  标準/典型的Reactor:

  - 步驟1:等待事件到來(Reactor負責)

  - 步驟2:将讀就緒事件分發給使用者定義的處理器(Reactor負責)

  - 步驟3:讀資料(使用者處理器負責)

  - 步驟4:處理資料(使用者處理器負責)

  改進實作的模拟Proactor:

  - 步驟1:等待事件到來(Proactor負責)

  - 步驟2:得到讀就緒事件,執行讀資料(現在由Proactor負責)

  - 步驟3:将讀完成事件分發給使用者處理器(Proactor負責)

  -步驟4:處理資料(使用者處理器負責)  

  對于不提供異步IO API的作業系統來說,這種辦法可以隐藏socketAPI的互動細節,進而對外暴露一個完整的異步接口。借此,我們就可以進一步建構完全可移植的,平台無關的,有通用對外接口的解決方案。

 上述方案已經由Terabit P/L公司(http://www.terabit.com.au/)實作為TProactor。它有兩個版本:C++和JAVA的。C++版本采用ACE跨平台底層類開發,為所有平台提供了通用統一的主動式異步接口。

  Boost.Asio庫,也是采取了類似的這種方案來實作統一的IO異步接口。

<-----------

最近在項目中使用了Boost.Asio類庫,其就是以Proactor這種設計模式來實作,參見:Proactor(TheBoost.Asio library is based on the Proactor pattern. This designnote outlines the advantages and disadvantages of thisapproach.),其設計文檔連結:http://asio.sourceforge.net/boost_asio_0_3_7/libs/asio/doc/design/index.html

First, let us examine how the Proactor design pattern isimplemented in asio, without reference to platform-specificdetails.

高性能I/O設計模式 reactor &amp; proactor

繼續閱讀