天天看點

高性能I/O設計模式Reactor和Proactor

昨天購買了《程式員》雜志 2007.4期,第一時間去翻閱了一遍,其中有一篇《兩種高性能I/O設計模式的比較》令人眼睛一亮,這是一篇譯文,偶最近在一直想認真看看這方面的文章很久了。

文章主要是講到了系統I/O方式可分為阻塞,非阻塞同步和非阻塞異步三類,三種方式中,非阻塞異步模式的擴充性和性能最好。主要是講了兩種IO多路複用模式:Reactor和Proactor,并對它們進行了比較。

文章還介紹了為Reactor和Proactor模式建構一個通用的,統一的對外接口并是一個完全可移植的開發架構選擇方案:​​TProactor​​​(ACE compatible Proactor) :​​http://www.terabit.com.au/solutions.php​​。因為Linux對aio支援的不完整,是以ACE_Proactor架構在linux上的表現很差,大部分在windows上執行正常的代碼,在Linux則運作異常,甚至不能編譯通過。這個問題一直困擾着很大多數ACE的使用者,現在好了,有一個TProactor幫助解決了在Linux不完整支援AIO的條件下,正常使用(至少是看起來正常)ACE_Proactor。

文章主要摘要:

---------->

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

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

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

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

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

 在Reactor中實作讀:

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

 - 事件分離器等待事件

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

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

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

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

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

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

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

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

實踐現狀 

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

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

 然而,并非所有的作業系統都在系統級大力支援異步。像很多Unix系統就沒做到。是以,在Unix上,選擇ACE Reactor解決方案可能更好。但這樣一來,為了獲得最好的性能,網絡應用的開發人員必須為不同的作業系統維護多份代碼:windows上以ACE Proactor為基礎,而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的作業系統來說,這種辦法可以隐藏socket API的互動細節,進而對外暴露一個完整的異步接口。借此,我們就可以進一步建構完全可移植的,平台無關的,有通用對外接口的解決方案。

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

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

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

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

First, let us examine how the Proactor design pattern is implemented in asio, without reference to platform-specific details.

高性能I/O設計模式Reactor和Proactor

Proactor design pattern (adapted from [1])

當然這兩I/O設計模式,也在ACE中被大量應用,這在ACE的相關書籍中都有介紹,其中在“ACE開發者”網站中有很多不錯的介紹文章。

​​​​

如:​​ACE技術論文集-第8章 前攝器(Proactor):用于為異步事件多路分離和分派處理器的對象行為模式​​

​​ACE技術論文集-第7章 ACE反應堆(Reactor)的設計和使用:用于事件多路分離的面向對象構架​​

​​ACE程式員教程-第6章 反應堆(Reactor):用于事件多路分離和分派的體系結構模式​​

​​ACE應用-第2章 JAWS:高性能Web伺服器構架​​

Proactor模式在單CPU單核系統應用中有着無可比拟的優勢,現在面臨的問題是:在多CPU多核的系統中,它如何更好地應用多線程的優勢呢???這是很值思考和實踐的,也許會産生另外一種設計模式來适應發展的需要啦。

昨天購買了《程式員》雜志 2007.4期,第一時間去翻閱了一遍,其中有一篇《兩種高性能I/O設計模式的比較》令人眼睛一亮,這是一篇譯文,偶最近在一直想認真看看這方面的文章很久了。

文章主要是講到了系統I/O方式可分為阻塞,非阻塞同步和非阻塞異步三類,三種方式中,非阻塞異步模式的擴充性和性能最好。主要是講了兩種IO多路複用模式:Reactor和Proactor,并對它們進行了比較。

文章還介紹了為Reactor和Proactor模式建構一個通用的,統一的對外接口并是一個完全可移植的開發架構選擇方案:​​TProactor​​​(ACE compatible Proactor) :​​http://www.terabit.com.au/solutions.php​​。因為Linux對aio支援的不完整,是以ACE_Proactor架構在linux上的表現很差,大部分在windows上執行正常的代碼,在Linux則運作異常,甚至不能編譯通過。這個問題一直困擾着很大多數ACE的使用者,現在好了,有一個TProactor幫助解決了在Linux不完整支援AIO的條件下,正常使用(至少是看起來正常)ACE_Proactor。

文章主要摘要:

---------->

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

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

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

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

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

 在Reactor中實作讀:

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

 - 事件分離器等待事件

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

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

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

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

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

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

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

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

實踐現狀 

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

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

 然而,并非所有的作業系統都在系統級大力支援異步。像很多Unix系統就沒做到。是以,在Unix上,選擇ACE Reactor解決方案可能更好。但這樣一來,為了獲得最好的性能,網絡應用的開發人員必須為不同的作業系統維護多份代碼:windows上以ACE Proactor為基礎,而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的作業系統來說,這種辦法可以隐藏socket API的互動細節,進而對外暴露一個完整的異步接口。借此,我們就可以進一步建構完全可移植的,平台無關的,有通用對外接口的解決方案。

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

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

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

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

First, let us examine how the Proactor design pattern is implemented in asio, without reference to platform-specific details.

高性能I/O設計模式Reactor和Proactor

Proactor design pattern (adapted from [1])

當然這兩I/O設計模式,也在ACE中被大量應用,這在ACE的相關書籍中都有介紹,其中在“ACE開發者”網站中有很多不錯的介紹文章。

​​​​

如:​​ACE技術論文集-第8章 前攝器(Proactor):用于為異步事件多路分離和分派處理器的對象行為模式​​

​​ACE技術論文集-第7章 ACE反應堆(Reactor)的設計和使用:用于事件多路分離的面向對象構架​​

​​ACE程式員教程-第6章 反應堆(Reactor):用于事件多路分離和分派的體系結構模式​​