1. 程式人生 > >我對Reactor,Proactor模式的一點理解

我對Reactor,Proactor模式的一點理解

之前便簡單使用過了 Boost.Asio 非同步 I/O 庫,一直很都好奇諸如 async_read() 之類的非同步函式是如何來實現的,於是我就開始了瘋狂地找資料。 從重新理解同步,非同步,阻塞,非阻塞到重溫 Linux 下5種經典的 I/O 模型,然後我找到了 Reactor,Proactor 這兩個模式。往下讀之前希望你已經認識了各種 I/O 模型。

想要真正實現一個非同步操作,OS 必須提供給我們支援非同步 I/O(由於非同步主要用於解決 I/O 的效能問題,這裡暫且只討論非同步 I/O 操作)的 API。

Windows平臺為我們提供了 Proactor 模式的非同步支援方式,Proactor 模式是什麼?

這裡我們假設有3個物件,使用者程序 a,核心 b,非同步讀事件 A:

1.使用者程序 a 通過核心 b 提供的 API 註冊了一個非同步讀事件 A,同時提供自己的記憶體空間等引數。

2.核心 b 執行註冊好的事件 A,這個非同步讀事件包括從埠將資料讀到記憶體緩衝區,再將記憶體緩衝區的資料讀到使用者程序的記憶體空間中。

3.核心 b 完成事件 A 後通知使用者程序 a,“你要的資料已經讀到你的記憶體裡了,你可以用了” ,這種方式通常是呼叫1裡註冊的回撥函式來實現的。

這整個的過程便是 Proactor 模式了,它使得編寫使用者程序的程式設計師完全不用去理會整個非同步過程,而只需要靜靜地等待結果並使用就好了。

而到了 Linux 平臺上,似乎就沒有這麼幸運了,Linux 支援地只是非完全非同步的 Reactor 模式,甚至更早的版本上連 Reactor 都不支援。為什麼說是非完全非同步(這個詞是我自己胡亂造的,只是為了更好地說明問題)?先來看一看一個 Reactor 模式下的非同步讀事件是如何完成的。

4個物件,使用者程序 a,核心 b,讀事件 A及其 Socket 控制代碼:

1.使用者程序 a 通過核心 b 提供的 API(如epoll_ctr()) 註冊了一個 Socket。

2.核心檢測到註冊過的 Socket 對應的埠有資料可讀時通知使用者程序 a,"你要的資料已經來了,你可以從埠讀取了"(注意和 Proactor 的區別),通常也是通過註冊的回撥函式來進行讀取。

到這裡你應該已經發現了2者的區別,Proactor 模式裡,使用者程序不需要關注資料讀取的細節且可以去幹別的事情,直接通過提前註冊好的回撥函式來使用就行了。這就好比你問別人借了一個東西,想讓他送到你家來,但是你那段時間卻想去幹別的事,於是你提前告訴他,“我家的鑰匙放在地毯下面,東西送過來了開門放到桌上就行”(回撥函式)。所以整個過程包括核心等待資料的到來和核心將資料從核心緩衝區 copy 至使用者記憶體空間使用者程序都可以去做別的事,都是非阻塞進行的。

而在 Reactor 模式中,雖然在等待資料到來這一過程核心會幫你完成,但是將資料從埠搬運到使用者的記憶體空間是需要使用者來完成的,通常我們可以利用回撥函式來完成第二個過程,而回調函式由誰來執行取決於具體實現了。還是用上面的例子,你問別人借了一個東西,想讓他送到你家來,但是你那段時間仍然想去幹別的事,於是你提前告訴他,“你儘管來吧,等你到的時候會有人拿的”(這個人可能就是指代跑回調函式的執行緒)。

講到這裡就可以解釋一下之前我胡亂編造的名詞“非完全非同步”了,首先再簡單解釋一下非同步:

將某個過程交於別人來完成,至於別人什麼時候才能完成我們管不著也不想管,我們可以安心地幹別的,只要完成的時候知會我一聲(或者是做我提前安排好的其他事)就可以了。

在 Reactor 模式中,資料等待的過程很明顯符合上面的定義,屬於非同步過程,而當資料到達後,使用者程序又不得不自己來完成讀取過程,而在 Linux 中,核心是不提供這一功能的。可能你會說:“找一個代理來完成讀取的過程不就OK了嗎?”,對的,這就是我下面要講的,通過這種方式來實現從 Reactor 模式到 Proactor 模式的模擬,這也正是 Asio 庫最終選擇 Proactor 模式的原因,可以跨平臺了(從 Windows 到 Linux)。

之後的內容純屬我的推測-。-,等我能力夠了再去讀 Asio 的原始碼,到時候回來再補

用過 Asio 庫的都會很熟悉 io_service,想要支援非同步操作就必須通過 io_service 例項的支援,那麼它便是上面所說到的代理了。

io_service 用來管理所有的非同步操作,在 Windows 平臺上,它的任務可能就是在核心將資料 copy 到使用者程序空間後將回調函式加入到任務佇列中並按順序執行這些回撥函式。而在 Linux 平臺上,它還需要完成再 Windows上本該屬於核心的工作,也就是將資料 copy 到使用者程序空間,然後再執行回撥函數了。能力有限,先講到這。。。