事件驅動及其設計模式

在GUI編程中,事件是很是常見的。好比,用戶在界面點擊了按鈕,就會發送一個「點擊」事件,而相應的會有一個處理「點擊」事件的事件處理器會來處理該事件。面試

所以,所謂事件驅動,簡單地說就是你點什麼按鈕(即產生什麼事件),電腦執行什麼操做(即調用什麼函數)。固然事件也不只限於用戶的操做. 事件驅動的核心天然是事件。從事件角度說,事件驅動程序的基本結構是由一個事件收集器、一個事件發送器和一個事件處理器組成。事件收集器專門負責收集全部事件,包括來自用戶的(如鼠標、鍵盤事件等)、來自硬件的(如時鐘事件等)和來自軟件的(如操做系統、應用程序自己等)。事件發送器負責將收集器收集到的事件分發到目標對象中。事件處理器作具體的事件響應工做,它每每要到實現階段才徹底肯定。對於框架的使用者來講,他們惟一可以看到的是事件處理器。這也是他們所關心的內容。編程

事件驅動編程

事件驅動編程一般只是用一個執行過程,CPU之間不是併發的,在處理多任務的時候,事件驅動編程是使用協做式處理任務,而不是多線程的搶佔式。事件驅動簡潔易用,只須要註冊感興趣的事件,在回調中設計邏輯就能夠了。在調用的過程當中,事件循環器(Event Loop)在等待事件的發生,跟着調用處理器。事件處理器不是搶佔式的,處理器通常只有很短的生命週期。設計模式

事件驅動編程的優點

l 在大部分的應用場景中,事件編程優與多線程編程。緩存

l 相對與多線程編程來說,事件驅動編程比較容易,複雜度低,是開發者樂於接受的。服務器

l 大多數的GUI框架,都是使用事件驅動編程了架構的。每個事件會綁定一個處理器,這些事件一般是點擊按鈕,選擇菜單,等等。處理器r來實現具體的行爲邏輯。網絡

l 事件驅動常用在I/O框架中,能夠很好的實現I/O複用。不少高性能的I/O框架都是使用事件驅動模型的,例如:Netty、Mina、Node.js。多線程

l 易於調試。時間依賴只和事件有關係,而不是內部調度。問題容易暴露。架構

事件驅動編程的劣勢

l 若是處理器佔用時間較長,那會阻塞應用程序的響應。併發

l 沒法經過時間來維護本地狀態,由於處理器必須返回。框架

l 一般在單CPU環境下,比多線程編程要快,由於沒有鎖的因素,沒有線程切換的損耗。CPU不是併發的,這樣的話就不適合用在一些科學計算的應用中。

事件循環器(Event Loop)的實現

事件循環器(Event Loop)是一個程序結構,用於等待和發送消息和事件。事件驅動編程的代碼核心就是事件循環器,在Linux下推薦使用epoll實現,在其它沒有epoll 的系統上可使用kqueue/ports/poll/select實現。

下圖是事件循環器的工做示例圖。事件循環器不斷接受來自客戶端(Client)的請求,事件循環器把請求轉交給註冊了某類事件的工做線程(Worker)處理。

 

根據實現的方式不一樣,在網絡編程中基於事件驅動主要有兩種設計模式:Reactor和Proactor。

Reactor

首先來回想一下普通函數調用的機制:

l  程序調用某函數->函數執行

l  程序等待->函數將結果

l  控制權返回給程序->程序繼續處理

和普通函數調用的不一樣之處在於:應用程序不是主動的調用某個API完成處理,而是偏偏相反,應用程序須要提供相應的接口並註冊到Reactor上,若是相應的事件發生,Reactor將主動調用應用程序註冊的接口,這些接口又稱爲「回調函數」。

用「好萊塢原則」來形容Reactor再合適不過了:不要打電話給咱們,咱們會打電話通知你。

舉個例子:你去應聘某xx公司,面試結束後。

l  「普通函數調用機制」公司HR比較懶,不會記你的聯繫方式,那怎麼辦呢,你只能面試完後本身打電話去問結果;有沒有被錄取啊,仍是被拒了;

l  「Reactor」公司HR就記下了你的聯繫方式,結果出來後會主動打電話通知你:有沒有被錄取啊,仍是被拒了;你不用本身打電話去問結果,事實上也不能,由於你沒有HR的聯繫方式。

Reactor模式的優勢

Reactor模式是編寫高性能網絡服務器的必備技術之一,它具備以下的優勢:

1)響應快,沒必要爲單個同步時間所阻塞,雖然Reactor自己依然是同步的;

2)編程相對簡單,能夠最大程度的避免複雜的多線程及同步問題,而且避免了多線程/進程的切換開銷;

3)可擴展性,能夠方便的經過增長Reactor實例個數來充分利用CPU資源;

4)可複用性,Reactor框架自己與具體事件處理邏輯無關,具備很高的複用性;

Reactor模式框架

使用Reactor模型,必備的幾個組件:事件源、Reactor框架、事件多路複用機制和事件處理程序,先來看看Reactor模型的總體框架,接下來再對每一個組件作逐一說明。

1)事件源:Linux上是文件描述符,Windows上就是Socket或者Handle了,這裏統一稱爲「句柄集」;程序在指定的句柄上註冊關心的事件,好比I/O事件。

2)事件多路複用機制:由操做系統提供的I/O多路複用機制,好比select和epoll。程序首先將其關心的句柄(事件源)及其事件註冊到多路複用機制上。當有事件到達時,事件多路複用機制會發出通知「在已經註冊的句柄集中,一個或多個句柄的事件已經就緒」。程序收到通知後,就能夠在非阻塞的狀況下對事件進行處理了。

3) Reactor。是事件管理的接口,內部使用事件多路複用機制註冊、註銷事件;並運行事件循環,當有事件進入「就緒」狀態時,調用註冊事件的回調函數處理事件。

4)事件處理程序。事件處理程序提供了一組接口,每一個接口對應了一種類型的事件,供Reactor在相應的事件發生時調用,執行相應的事件處理。一般它會綁定一個有效的句柄。

使用Reactor模式後,事件控制流是什麼樣子呢?能夠參見下面的序列圖。

咱們分別以讀操做和寫操做爲例來看看Reactor中的具體步驟:

1) 應用程序註冊讀就緒事件和相關聯的事件處理器;

2) 事件分離器等待事件的發生;

3) 當發生讀就緒事件的時候,事件分離器調用第一步註冊的事件處理器;

4) 事件處理器首先執行實際的讀取操做,而後根據讀取到的內容進行進一步的處理。

寫入操做相似於讀取操做,只不過第一步註冊的是寫就緒事件。

Proactor

咱們來看看Proactor模式中讀取操做和寫入操做的過程:

1) 應用程序初始化一個異步讀取操做,而後註冊相應的事件處理器,此時事件處理器不關注讀取就緒事件,而是關注讀取完成事件,這是區別於Reactor的關鍵。

2) 事件分離器等待讀取操做完成事件。

3) 在事件分離器等待讀取操做完成的時候,操做系統調用內核線程完成讀取操做(異步I/O都是操做系統負責將數據讀寫到應用傳遞進來的緩衝區供應用程序操做),並將讀取的內容放入用戶傳遞過來的緩存區中。這也是區別於Reactor的一點。

4) 事件分離器捕獲到讀取完成事件後,激活應用程序註冊的事件處理器,事件處理器直接從緩存區讀取數據,而不須要進行實際的讀取操做。

Proactor中寫入操做和讀取操做,只不過感興趣的事件是寫入完成事件。

從上面能夠看出,Reactor和Proactor模式的主要區別就是真正的讀取和寫入操做是有誰來完成的,Reactor中須要應用程序本身讀取或者寫入數據,而Proactor模式中,應用程序不須要進行實際的讀寫過程,它只須要從緩存區讀取或者寫入便可,操做系統會讀取緩存區或者寫入緩存區到真正的I/O設備。

參考引用

l  《Netty原理解析與開發實戰》

l  《分佈式系統經常使用技術及案例分析(第二版)》

相關文章
相關標籤/搜索