1. 程式人生 > >node.js 事件驅動

node.js 事件驅動

在傳統程式設計中,i/o操作和本地函式呼叫的處理方式相同:處理過程需要一直等待直到某個操作結束才能繼續下去,這種基於i/o操作的阻塞式程式設計模型繼承自早期的分時系統,在這類系統中,每一個程序都對應著一個使用者,這樣做的目的是使得使用者之間相互隔離。並且在這類系統中,使用者在決定下一個操作前,必須先結束一個操作。顯然,這種“單使用者,單程序”的模型不具備較好的伸縮性,當任務的數量達到一定的數量之後效能開始急劇下降

除了上述程式設計模型之外,多執行緒程式設計模型是另外一個選擇。執行緒可以看作是一種輕量級的程序,它與同一程序裡的其他執行緒共享記憶體。執行緒作為早期模型的拓展而被建立,以適應若干併發執行緒的執行

。當一個執行緒等待i/o操作時,另一個執行緒就可以接管CPU,等待執行緒將i/o操作結束時被喚醒。多執行緒程式設計的缺點是什麼?程式設計師並不知道在給定時刻究竟是哪些執行緒在執行,所以他們必須仔細處理對共享記憶體狀態的併發訪問,使用諸如執行緒鎖和訊號量這樣的同步原語協調對某些資料結構的訪問,強制他們對執行緒執行排程的各種途徑進行預測以避免問題的發生。假如應用程式非常依賴於執行緒間的共享狀態,那麼這種程式設計方式容易導致隨機發生的危險錯誤,並且這些錯誤很難查詢。

如果不讓作業系統進行執行緒排程,那麼程式設計師的另一個選擇是協同多執行緒,在這種模式下,程式設計師負責顯式地讓CPU為其他程序的執行分配時間,因為是人為負責執行緒排程,所以可以放寬同步需求,但是基於和普通多執行緒同樣的原因,這種方法也很複雜並且容易出錯

說了這麼多,其實事件驅動程式設計是指程式的執行流程取決於事件的程式設計風格,事件由事件處理程式或者事件回撥函式進行處理。當某些重要的事件發生時——例如資料庫查詢結果或者使用者單擊了某個按鈕,就會呼叫事件回撥函式。以下面的程式碼為例,看看在典型的阻塞式i/o模型裡,如何實現對資料庫的查詢:

    var result=query('select * from posts where id=1');
    do_something_with(result);
  • 1
  • 2

顯然,上述查詢過程需要當前執行緒或者程序一直等待下去,直到資料庫層完成對資料的查詢處理為止,而在事件驅動系統中,上述查詢則以如下方式進行:

    query_finished=function(result){
        do_something_with(result);
    }
    query('select * from posts where id=1',query_finished);
  • 1
  • 2
  • 3
  • 4

簡單解釋一下:此次首先定義了在查詢完成以後將會發生的處理過程,並且將這個過程儲存在一個名為query_finished 的函式當中,然後講函式名作為查詢的一個引數。這樣當查詢完成後將會呼叫query_finished 函式,而不僅僅只是返回查詢結果。這種程式設計風格的好處就是:當前程序在處理i/o操作時不會發生阻塞,因此多個i/o操作可以並行進行,當每個操作結束時將會分別呼叫其對應的回撥函式

再補充一個概念:事件迴圈,事件迴圈和事件驅動相伴相生,包括事件監測和事件觸發處理,在每一輪迴圈中它必須檢測發生了什麼事件,呼叫了哪個回撥函式。事件迴圈只是在一個程序中執行的單個執行緒,這意味著當事件發生時,可以不用中斷就執行時間處理程式,這樣做的好處有兩個: 
1. 在任一給定時刻,最多執行一個事件處理程式。 
2. 事件處理程式可以不間斷地執行直到結束。 
這使得程式設計師能夠放寬同步要求,並且不必擔心執行併發執行緒會改變共享記憶體的狀態