關於事件模型的一些見解

概述

事件處理模型, 也便是全異步事件處理模型。在之前, 對於那些同時執行多項任務, 但仍能響應用戶交互的應用程序一般須要實施一種使用多進程(如linux的fork操做)或者多線程的操做。對於低併發的環境, 這樣作無疑能避免進程因等待某個操做而出現"假死"現象。但對於更復雜的異步應用程序或者是要求高併發的環境, 就要使用事件模型來處理異步事件, 這樣作有不少好處:javascript

  • 在高併發條件下響應用戶時間更快;
  • 內存消耗下降, 能處理更多的用戶請求;
  • 單進程, 在高併發下避免由於線程或進程之間切換帶來的時間片消耗;
  • 有效下降死鎖發生的頻率

固然, 基於事件的處理模型也有其缺點:html

  • 事件處理器在處理的時候要儘可能的快, 不然就會阻塞主線程;
  • 進程會開闢額外的內存空間來維護請求狀態;
  • 事件模型通常是單進程操做, 不能有效利用多核cpu的性能(能夠經過建立子線程解決問題)

當前, 基於事件的處理模型被應用到了各類環境之中。譬如linux的epoll模塊, javascript語言以及Nginx服務器等。java

本文地址: http://www.cnblogs.com/blackmanba/p/3676636.html或者http://forkme.info/about-event-loop/, 轉載請註明源地址。linux

事件處理模型特色

<script type="text/javascript">
var callback = function () {
    alert('Hello world');
};

doucment.onclick = callback;
......
</script>

這就是實現事件處理模型的通常模式: 首先定義一個或多個異步事件, 每個事件綁定回調函數。主進程註冊完事件後就繼續執行, 只有當事件被觸發時纔會執行回調函數。放到例子當中就是當用戶點擊文檔時會彈出窗口並顯示 Hello world。服務器

綜上: 基於事件模型的程序要具有如下結構, 如圖:多線程

event loop

    1. 事件源: 就是事件的來源或者事件的產生者, 程序須要對事件源進行操做;
    1. 事件分離器: 獲取事件池中的事件並根據事件源找到不一樣的事件處理器按順序進行處理;
    1. 事件處理器: 便是上文說到的回調函數, 用於處理具體業務;
    1. 事件循環: 週期性的獲取事件源中的事件進行分發

事件模型實現原理

1. Event Loop

Event Loop是一個很重要的概念, 指的是計算機系統實現的一種運行方式, 主要用於等待併發送消息和事件。雖然在每一種實現方式中具體的實現方法可能不一樣(例如Nginx事件模型和javascript事件模型), 可是都須要實現並維護Event Loop。
在計算機中, 每個運行的程序就叫作進程(process)。 通常狀況下, 一個進程只能一次只能執行一個任務。若是想要執行多個任務, 不外乎如下三種解決方式:併發

  • 1 排隊: 每次進程只能執行一個任務, 只好等前面的任務執行完了, 再執行後面的任務;
  • 2 新建進程: 對於每一次請求fork新的進程進行處理;
  • 3 新建線程: 對於每個請求新建線程進行處理

以上2和3方式分別對應多進程和多線程模型, 優劣在以前已經講述。而事件處理模型正是基於上面第一種方式, 這樣作能夠避免沒必要要的進程或線程間切換, 保證進程的高效運行。可是有一個問題, 一旦遇到大量任務或者是遇到一個耗時的操做, 進程就會被阻塞, 出現所謂的"假死"現象, 沒法響應其餘操做。異步

按照上面的運行模型, 若是某個任務很耗時,那麼進程的運行大概是這個樣子:async

Block

上圖的綠色部分是程序運行時間, 紅色部分是等待時間。這個進程在大部分時間都在等待其餘操做, 這種運行方式稱爲"同步模式"(synchronous I/O)或"堵塞模式"(blocking I/O)。函數

若是採用多進程或者多線程, 極可能就是下面這種狀況:

Block

上圖代表, 比起單進程, 多進程耗費成倍的系統資源並閒置等待, 這顯然不合理。

Event Loop就是爲了解決單進程阻塞問題而提出來的一種合理方案。以javascript做爲例子, 在程序中設置一個主進程和若干個子線程(以1個子線程爲例)。主進程負責程序自己的運行並處理"非阻塞"操做; 子線程負責與其餘進程(主要是I/O操做等耗時進程)的通訊, 這個線程就被稱爲"Event Loop線程"。

non block

上圖橙色表示空閒時間。每當遇到I/O操做等耗時操做時, 主進程就讓Event Loop線程去通知相應的I/O程序並註冊相應的事件和設置回調函數, 而後主線程接着日後運行, 因此不出現紅色的阻塞時間, 等到I/O操做完成, Event Loop線程再把結果返回主進程, 主進程調用實現設定的回調函數完成任務(注意: 回調函數不能執行太多的耗時操做, 由於此時主進程是處於阻塞狀態的)。這種運行方式稱爲"異步模式"(asynchronous I/O)或"非堵塞模式"(non-blocking mode)。這也是事件處理模型的內部實現機制。

2. 上下文(Context)

(1)上下文含義

在計算機中, 上下文表明着不少種含義。在基於事件的模型中,上下文表示什麼?簡單的講, 以javascript爲例, 就是在主進程註冊事件後將回調函數做用域內的變量保存成一個對象保存起來, 這個對象就叫作上下文對象。每個事件的回調函數都包含着本身的上下文對象, 當對應事件被調用並執行回調函數時, 函數能夠直接經過上下文對象得到做用域內保存變量的值, 這個操做對開發者是透明的, 開發着只須要直接寫對象名稱就能夠了。

(2)爲何要定義上下文

在基於事件的模型中, 上下文是必須存在的, 爲何要定義上下文這個概念呢?由於基於事件的模型都是基於異步機制的。以javascript爲例, 每次主進程執行到結尾的時候就會釋放其做用域內的全部變量。換句話說, 無論事件有沒有被觸發, 主進程執行完全部的邏輯以後就會銷燬全部的變量。若是沒有上下文的話, 當用戶觸發事件執行回調函數時, 就會沒法找到其做用域內的變量, 使用上下文就是爲了讓每個特定的回調函數能訪問到屬於本身做用域內的變量的值。

權衡線程數

在實現一個基於事件模型的應用的過程當中, 有一個問題是必須考慮到的, 那就是主進程究竟要建立多少子線程才比較合理, 也就是說Event Loop何時須要建立。若是建立的子線程太多或是太少, 會出現如下問題:

  • 1 線程建立太多: 線程越多, 線程間的切換就越頻繁, cpu就會消耗更多的時間在線程間的切換上。致使實際處理邏輯執行時間太短;
  • 2 線程建立太少: 線程越少, 就會有越多的阻塞操做集中在同一個線程中, 會影響同一線程內其餘阻塞操做的執行

子線程的數目主要要根據應用處理的業務類型, 具體機器的內存和cpu等因素決定, 並無一致的規定。如下作法是比較好的一種實踐:

  • 對於大文件上傳, 下載等耗時比較長的操做建議是每次建立新的線程來處理;
  • 根據業務類型, 用戶比較急需的阻塞操做能夠獨開線程處理;
  • 對於耗時較短並且是不固定時間的操做, 好比點擊事件等, 能夠用一個Event Loop來放置這些阻塞事件

總結

對於高併發, 多請求的事件, 傳統的多線程和多進程的方法已經難以應付這些狀況。而基於事件的模型因爲採用的是異步的方式, 經過相似於中斷上下文的操做, 能得到很好的併發性和高性能。而且能避免以前模型出現的一些棘手問題, 好比死鎖問題。隨着應用複雜度飛不斷提升, 相信會有更多的應用將會採用基於事件的模型來應對這些狀況。

相關文章
相關標籤/搜索