2种IO并发开发中的设计模式:Reactor and Proactor
Reactor and Proactor
[原文:http://blog.csdn.net/wenbingoon/article/details/9880365]
? ?? 1 概述
IO讀寫(xiě)時(shí),多路復(fù)用機(jī)制都會(huì)依賴對(duì)一個(gè)事件多路分離器,負(fù)責(zé)把源事件的IO 事件分離出來(lái),分別到相應(yīng)的read/write事件分離器。涉及到事件分離器的兩種模式分別就是 Reactor和Proactor,Reactor是基于同步IO的,Proactor是基于異步IO的。
在Reactor模式中,事件分離者等待某個(gè)事件或者可應(yīng)用或個(gè)操作的狀態(tài)發(fā)生(比如文件描述符可讀寫(xiě),或者是socket可讀寫(xiě)),事件分離者就把這個(gè)事件傳給事先注冊(cè)的事件處理函數(shù)或者回調(diào)函數(shù),由后者來(lái)做實(shí)際的讀寫(xiě)操作。Reactor模式主要是提高系統(tǒng)的吞吐量,理解反應(yīng)器模式的例子:Reactor模式,或者叫反應(yīng)器模式
在Proactor模式中,事件處理者(或者代由事件分離者發(fā)起)直接發(fā)起一個(gè)異步讀寫(xiě)操作(相當(dāng)于請(qǐng)求),而實(shí)際的工作是由操作系統(tǒng)來(lái)完成的。發(fā)起時(shí),需要提供的參數(shù)包括用于存放讀到數(shù)據(jù)的緩存區(qū),讀的數(shù)據(jù)大小,或者用于存放外發(fā)數(shù)據(jù)的緩存區(qū),以及這個(gè)請(qǐng)求完后的回調(diào)函數(shù)等信息。事件分離者得知了這個(gè)請(qǐng)求,它默默等待這個(gè)請(qǐng)求的完成,然后轉(zhuǎn)發(fā)完成事件給相應(yīng)的事件處理者或者回調(diào)。舉例來(lái)說(shuō),在Windows上事件處理者投遞了一個(gè)異步IO操作(稱有 overlapped的技術(shù)),事件分離者等IOCompletion事件完成. 這種異步模式的典型實(shí)現(xiàn)是基于操作系統(tǒng)底層異步API的,所以我們可稱之為“系統(tǒng)級(jí)別”的或者“真正意義上”的異步,因?yàn)榫唧w的讀寫(xiě)是由操作系統(tǒng)代勞的。
舉個(gè)例子,將有助于理解Reactor與Proactor二者的差異,以讀操作為例(類操作類似)。
在Reactor中實(shí)現(xiàn)讀:
- 注冊(cè)讀就緒事件和相應(yīng)的事件處理器
- 事件分離器等待事件
- 事件到來(lái),激活分離器,分離器調(diào)用事件對(duì)應(yīng)的處理器。
- 事件處理器完成實(shí)際的讀操作,處理讀到的數(shù)據(jù),注冊(cè)新的事件,然后返還控制權(quán)。
與如下Proactor(真異步)中的讀過(guò)程比較:
- 處理器發(fā)起異步讀操作(注意:操作系統(tǒng)必須支持異步IO)。在這種情況下,處理器無(wú)視IO就緒事件,它關(guān)注的是完成事件。
- 事件分離器等待操作完成事件
- 在分離器等待過(guò)程中,操作系統(tǒng)利用并行的內(nèi)核線程執(zhí)行實(shí)際的讀操作,并將結(jié)果數(shù)據(jù)存入用戶自定義緩沖區(qū),最后通知事件分離器讀操作完成。
- 事件分離器呼喚處理器。
- 事件處理器處理用戶自定義緩沖區(qū)中的數(shù)據(jù),然后啟動(dòng)一個(gè)新的異步操作,并將控制權(quán)返回事件分離器。
可以看出,兩個(gè)模式的相同點(diǎn),都是對(duì)某個(gè)IO事件的事件通知(即告訴某個(gè)模塊,這個(gè)IO操作可以進(jìn)行或已經(jīng)完成)。在結(jié)構(gòu)上,兩者也有相同點(diǎn):demultiplexor負(fù)責(zé)提交IO操作(異步)、查詢?cè)O(shè)備是否可操作(同步),然后當(dāng)條件滿足時(shí),就回調(diào)handler;
? 不同點(diǎn)在于,異步情況下(Proactor),當(dāng)回調(diào)handler時(shí),表示IO操作已經(jīng)完成;同步情況下(Reactor),回調(diào)handler時(shí),表示IO設(shè)備可以進(jìn)行某個(gè)操作(can read or can write),handler這個(gè)時(shí)候開(kāi)始提交操作。
2、Reactor模式
?? ??Reactor釋義“反應(yīng)堆”,是一種事件驅(qū)動(dòng)機(jī)制。和普通函數(shù)調(diào)用的不同之處在于:應(yīng)用程序不是主動(dòng)的調(diào)用某個(gè)API完成處理,而是恰恰相反,Reactor逆置了事件處理流程,應(yīng)用程序需要提供相應(yīng)的接口并注冊(cè)到Reactor上,如果相應(yīng)的時(shí)間發(fā)生,Reactor將主動(dòng)調(diào)用應(yīng)用程序注冊(cè)的接口,這些接口又稱為“回調(diào)函數(shù)”。使用Libevent也是想Libevent框架注冊(cè)相應(yīng)的事件和回調(diào)函數(shù);當(dāng)這些時(shí)間發(fā)聲時(shí),Libevent會(huì)調(diào)用這些回調(diào)函數(shù)處理相應(yīng)的事件(I/O讀寫(xiě)、定時(shí)和信號(hào))。
?? ?用“好萊塢原則”來(lái)形容Reactor再合適不過(guò)了:不要打電話給我們,我們會(huì)打電話通知你。?
3、兩個(gè)模式簡(jiǎn)單對(duì)比
?? ? 兩個(gè)模式的相同點(diǎn):(1)都是對(duì)某個(gè)IO事件的事件通知(即告訴某個(gè)模塊,這個(gè)IO操作可以進(jìn)行或已經(jīng)完成)。(2)在結(jié)構(gòu)上的相同點(diǎn):demultiplexor負(fù)責(zé)提交IO操作(異步)、查詢?cè)O(shè)備是否可操作(同步),然后當(dāng)條件滿足時(shí),就回調(diào)handler。
?? ? 不同點(diǎn)在于:異步情況下(Proactor),當(dāng)回調(diào)handler時(shí),表示IO操作已經(jīng)完成;同步情況下(Reactor),回調(diào)handler時(shí),表示IO設(shè)備可以進(jìn)行某個(gè)操作(can read or can write),handler這個(gè)時(shí)候開(kāi)始提交操作。
?? ? 我的理解:兩者的根本區(qū)別就在于《Unix網(wǎng)絡(luò)編程第一卷:套接口API》第6章講解的五種I/O模型,Proactor是基于異步I/O,Reactor是同步I/O(一般是I/O復(fù)用)。但是現(xiàn)在的操作系統(tǒng)并不是都能很好的真正支持異步I/O,比如Windows里有真正的異步I/O——IOCP,而Unix、Linux并沒(méi)有真正實(shí)現(xiàn)異步I/O。所以考慮程序移植性以及現(xiàn)在很多服務(wù)器基于Unix,Linux;Proactor封裝了這種差異,在內(nèi)部異步事件分離器實(shí)現(xiàn)時(shí)根據(jù)系統(tǒng)的不同調(diào)用相應(yīng)的I/O模式。
二、BIO、NIO、AIO
?NIO通常采用Reactor模式,AIO通常采用Proactor模式。AIO簡(jiǎn)化了程序的編寫(xiě),stream的讀取和寫(xiě)入都有OS來(lái)完成,不需要像NIO那樣子遍歷Selector。Windows基于IOCP實(shí)現(xiàn)AIO,Linux只有eppoll模擬實(shí)現(xiàn)了AIO。
Java7之前的JDK只支持NIO和BIO,從7開(kāi)始支持AIO。
4種通信方式:TCP/IP+BIO, TCP/IP+NIO, UDP/IP+BIO, UDP/IP+NIO。
TCP/IP+BIO、
Socket和ServerSocket實(shí)現(xiàn),ServerSocket實(shí)現(xiàn)Server端端口監(jiān)聽(tīng),Socket用于建立網(wǎng)絡(luò)IO連接。
不適用于處理多個(gè)請(qǐng)求 1.生成Socket會(huì)消耗過(guò)多的本地資源。2. Socket連接的建立一般比較慢。
BIO情況下,能支持的連接數(shù)有限,一般都采取accept獲取Socket以后采用一個(gè)thread來(lái)處理,one connection one thread。無(wú)論連接是否有真正數(shù)據(jù)請(qǐng)求,都需要獨(dú)占一個(gè)thread。
可以通過(guò)設(shè)立Socket池來(lái)一定程度上解決問(wèn)題,但是使用池需要注意的問(wèn)題是:1. 競(jìng)爭(zhēng)等待比較多。 2. 需要控制好超時(shí)時(shí)間。
TCP/IP+NIO
使用Channel(SocketChannel和ServerSocketChannel)和Selector。
Server端通常由一個(gè)thread來(lái)監(jiān)聽(tīng)connect事件,另外多個(gè)thread來(lái)監(jiān)聽(tīng)讀寫(xiě)事件。這樣做的好處是這些連接只有在真是請(qǐng)求的時(shí)候才會(huì)創(chuàng)建thread來(lái)處理,one request one thread。這種方式在server端需要支持大量連接但這些連接同時(shí)發(fā)送請(qǐng)求的峰值不會(huì)很多的時(shí)候十分有效。
UDP/IP+BIO
DatagramSocket和DatagramPacket。DatagramSocket負(fù)責(zé)監(jiān)聽(tīng)端口以及讀寫(xiě)數(shù)據(jù),DatagramPacket作為數(shù)據(jù)流對(duì)象進(jìn)行傳輸。
UDP/IP是無(wú)連接的,無(wú)法進(jìn)行雙向通信,除非雙方都成為UDP Server。
UDP/IP+NIO
通過(guò)DatagramChannel和ByteBuffer實(shí)現(xiàn)。DatagramChannel負(fù)責(zé)端口監(jiān)聽(tīng)及讀寫(xiě)。ByteBuffer負(fù)責(zé)數(shù)據(jù)流傳輸。
如果要將消息發(fā)送到多臺(tái)機(jī)器,如果為每個(gè)目標(biāo)機(jī)器都建立一個(gè)連接的話,會(huì)有很大的網(wǎng)絡(luò)流量壓力。這時(shí)候可以使用基于UDP/IP的Multicast協(xié)議傳輸,Java中可以通過(guò)MulticastSocket和DatagramPacket來(lái)實(shí)現(xiàn)。
Multicast一般多用于多臺(tái)機(jī)器的狀態(tài)同步,比如JGroups。SRM, URGCP都是Multicast的實(shí)現(xiàn)方式。eBay就采用SRM來(lái)實(shí)現(xiàn)將數(shù)據(jù)從主數(shù)據(jù)庫(kù)同步到各個(gè)搜索節(jié)點(diǎn)機(jī)器。
------------------
就IO而言:概念上有5中模型:blocking I/O,nonblocking I/O,I/O multiplexing (select and poll),signal driven I/O (SIGIO),asynchronous I/O (the POSIX aio_functions)。
不同的操作系統(tǒng)對(duì)上述模型支持不同: unix支持io多路復(fù)用,不同系統(tǒng)叫法不同 :freebsd里面叫 kqueue;linux 是epoll。而windows: 2000的時(shí)候就誕生了IOCP支持最后一種異步I/O
java是一種跨平臺(tái)語(yǔ)言,為了支持異步IO,誕生了nio,Java1.4引入的NIO 1.0是基于I/O復(fù)用的。在各個(gè)平臺(tái)上會(huì)選擇不同的復(fù)用方式。Linux用的epoll,BSD上用kqueue,Windows上應(yīng)該是重疊I/O(肯定不是IOCP)。
?
NIO 2.0(Java1.7)里終于有AIO了,Linux上用AIO,Windows上用IOCP,都支持了概念上的最后一種IO -- asynchronous I/O?
-------------------
[原文: http://blog.sina.com.cn/s/blog_9a97a37c0101aahl.html]
Reactor and Proactor兩個(gè)模式的相同點(diǎn),都是對(duì)某個(gè)IO事件的事件通知(即告訴某個(gè)模塊,這個(gè)IO操作可以進(jìn)行或已經(jīng)完成)。在結(jié)構(gòu)上,兩者也有相同點(diǎn):demultiplexor負(fù)責(zé)提交IO操作(異步)、查詢?cè)O(shè)備是否可操作(同步),然后當(dāng)條件滿足時(shí),就回調(diào)handler。
不同點(diǎn)在于,異步情況下(Proactor),當(dāng)回調(diào)handler時(shí),表示IO操作已經(jīng)完成;同步情況下(Reactor),回調(diào)handler時(shí),表示IO設(shè)備可以進(jìn)行某個(gè)操作(can read or can write),handler這個(gè)時(shí)候開(kāi)始提交操作。
用select模型寫(xiě)個(gè)簡(jiǎn)單的reactor,大致為:
///class?handler
{
public:
????virtual?void?onRead()?=?0;
????virtual?void?onWrite()?=?0;
????virtual?void?onAccept()?=?0;
};?
class?dispatch
{
public:
????void?poll()
????{
????????//?add?fd?in?the?set.
????????//
????????//?poll?every?fd
????????int?c?=?select(?0,?&read_fd,?&write_fd,?0,?0?);
????????if(?c?>?0?)
????????{
????????????for?each?fd?in?the?read_fd_set
????????????{????if?fd?can?read
????????????????????_handler->onRead();
????????????????if?fd?can?accept
????????????????????_handler->onAccept();
????????????}?
????????????for?each?fd?in?the?write_fd_set
????????????{
????????????????if?fd?can?write
????????????????????_handler->onWrite();
????????????}
????????}
????}?
????void?setHandler(?handler?*_h?)
????{
????????_handler?=?_h;
????}?
private:
????handler?*_handler;
};?
///?application
class?MyHandler?:?public?handler
{
public:
????void?onRead()
????{
????}?
????void?onWrite()
????{
????}?
????void?onAccept()
????{
????}
};?
在網(wǎng)上找了份Proactor模式比較正式的文檔,其給出了一個(gè)總體的UML類圖,比較全面:
根據(jù)這份圖我隨便寫(xiě)了個(gè)例子代碼:
class?AsyIOProcessor{
public:
????void?do_read()
????{
????????//send?read?operation?to?OS
????????//?read?io?finished.and?dispatch?notification
????????_proactor->dispatch_read();
????}?
private:
????Proactor?*_proactor;
};?
class?Proactor
{
public:
????void?dispatch_read()
????{
????????_handlerMgr->onRead();
????}?
private:
????HandlerManager?*_handlerMgr;
};?
class?HandlerManager
{
public:
????typedef?std::list<Handler*>?HandlerList;?
public:
????void?onRead()
????{
????????//?notify?all?the?handlers.
????????std::for_each(?_handlers.begin(),?_handlers.end(),?onRead?);
????}?
private:
????HandlerList?*_handlers;
};?
class?Handler
{
public:
????virtual?void?onRead()?=?0;
};?
//?application?level?handler.
class?MyHandler?:?public?Handler
{
public:
????void?onRead()?
????{
????????//?
????}
};?
Reactor通過(guò)某種變形,可以將其改裝為Proactor,在某些不支持異步IO的系統(tǒng)上,也可以隱藏底層的實(shí)現(xiàn),利于編寫(xiě)跨平臺(tái)
代碼。我們只需要在dispatch(也就是demultiplexor)中封裝同步IO操作的代碼,在上層,用戶提交自己的緩沖區(qū)到這一層,
這一層檢查到設(shè)備可操作時(shí),不像原來(lái)立即回調(diào)handler,而是開(kāi)始IO操作,然后將操作結(jié)果放到用戶緩沖區(qū)(讀),然后再
回調(diào)handler。這樣,對(duì)于上層handler而言,就像是proactor一樣。
----
其他參考:
兩種高性能I/O設(shè)計(jì)模式(Reactor/Proactor)的比較? http://blog.sina.com.cn/s/blog_5f435c130101ktl6.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)
總結(jié)
以上是生活随笔為你收集整理的2种IO并发开发中的设计模式:Reactor and Proactor的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 微博即将宕机?胡歌官宣已当爸爸 评论区直
- 下一篇: 离大谱 一家7口在高速应急车道内“涮火锅