1. 程式人生 > >js事件循環機制辨析

js事件循環機制辨析

時間 接受 context worker 簡易 接下來 能夠 請求 doc

?對於新接觸js語言的人來說,最令人困惑的大概就是事件循環機制了。最開始這也困惑了我好久,花了我幾個月時間通過書本,打代碼,查閱資料不停地漸進地理解他。接下來我想要和大家分享一下,雖然可能有些許錯誤的地方,希望大家不吝賜教,感謝感謝。

?這是所涉及的知識點:

  • 觀察者模式
  • js的事件循環機制
    • js事件循環機制優缺點及與多線程的比較

觀察者模式

?js的事件循環機制是基於觀察者模式的,而跟觀察者模式相對應的是輪詢,我們先來說說輪詢的原理。

?我們將輪詢映射在現實世界中即為:B不停到A的房間觀察房間裏是否有人,從而知道A是否回來。

?但顯然,這是效率極低的,我們回到代碼層面上。B線程使用while(true){觀察A的房間,當A在房間內時退出循環}來做到輪詢。但是,這樣B線程就被堵塞

住了,除非退出該循環,否則無法執行接下來的同步代碼及異步代碼。這對於單線程語言是完全無法接受的,所以我們來看看觀察者模式,他是否會堵塞線程。

?同樣的,我們來將觀察者模式映射到現實世界中:B在自己房間做自己的事情,不再不停地到A的房間看他是否回來,而是當A回到自己房間時,打電話通知B他回來了,B再去房間找A玩。

?該模式最大的優勢就是:B可以在等待A回房間的期間,做自己的事情。回到代碼層面上,使用觀察者模式後,B線程不再被堵塞,A回到房間的信息不再需要B通過循環來同步地監聽,而是A用消息傳給B線程,B再根據這個消息來執行當A回到房間後應該執行的操作。

?其實當理解了觀察者模式的大體流程就已經能夠理解js的事件循環機制了。但了解得深入些也沒有壞處。接下來我們來用js代碼來模擬出一個簡易的觀察者模式。
代碼如下:

var b = {
process_a:mes=>{
    console.log(‘剛剛A發了 %s 的信息,所以我知道A回來了,我該去他房間找他玩了。‘,mes)
}
}

function A(b){
    var mes_a = ‘我是A,我回來了‘
    b.process_a(mes_a)
}

A(b)

?結果如下:
技術分享圖片

?如果大家對同步,異步,堵塞,非堵塞的概念有不理解的地方的話,可以看我的 同步,異步,堵塞,非堵塞,並發 辨析。


事件循環機制

?事件循環機制的核心就是觀察者模式。我先給大家描述一遍程序執行的流程。

  1. js程序進入線程,函數入棧,當遇到同步代碼的時候就順序執行,遇到異步代碼時,把異步任務拋給WebAPIs執行,然後繼續執行接下來的同步代碼,直到棧為空。(如若大家對函數棧不了解的話可以看下我的 棧,堆辨析及使用)
  2. 在步驟1進行的同時,WebAPIs執行異步任務,當執行完一個異步任務就將其對應的回調函數放入任務隊列(Callback Queue)中等待。
    • WebAPIs是由C++實現的瀏覽器創建的線程,處理諸如DOM事件,http請求,定時器等異步任務。
  3. 當執行棧為空時,從Callback Queue中取出隊列頭放入執行棧中,回到第一步。

?給大家一個我畫的圖,方便理解。

技術分享圖片

?不過大家可能會疑惑,事件循環機制跟觀察者模式哪有什麽關系?其實是這樣的,在第2步中我寫道

當執行完一個異步任務就將其對應的回調函數放入任務隊列(Callback Queue)中。

?但我們是如何判斷這個異步任務執行完了呢——觀察者模式。任務隊列是觀察者,WebAPIs是被觀察者,觀察者要求被觀察者當發生執行完異步任務這一事件時,通知他執行完了,並將該事件對應的回調函數傳過來。

js事件循環機制優缺點及與多線程的比較

?通過事件循環機制,我們就可以實現代碼的異步,從而不會堵塞線程。

?通過這一特性,

  1. js在IO上有著卓越的表現,因為IO操作不再會堵塞住線程。
  2. 可以做到高並發。稍微解釋一下為什麽能夠高並發——當同時有多個任務要執行,js將他一一排列起來,然後按順序執行,這樣cpu就不會因為同時要處理的工作太多而負載過大。

?樸靈在《深入淺出nodeJS》中說道:

石器時代:同步。青銅時代:復制線程。白銀時代:多線程。黃金時代:事件驅動。

?不過我不敢說事件驅動就是比多線程好,但他確實沒有多線程的這些惱人的缺陷。

  1. 如果有大量的線程,會影響性能,因為操作系統需要在線程之間不停進行上下文切換。
  2. 通常數據是多個線程共享的,需要上鎖,同時又要防止出現死鎖現象。
  3. IO會堵塞住一個線程。

?但同時的,js也有他的缺陷。

  1. 不適合cpu密集型。也解釋一下——如一段代碼需要非常大量的計算量,以至於他長時間地占著線程,這就堵塞了,後繼的同步代碼及異步代碼都無法執行。不過,html5推出了web worker,可以有效地解決這一缺陷,在本章不表,後面我會專門寫一篇文章來講他。
  2. 只能使用一個線程,無法充分利用計算機的多核cpu。
  3. 可靠性低,一旦一個環節崩潰則整個程序全部崩潰。

?沒有一項技術是絕對完美的,但我們要清楚他的優缺點及原因,從而能夠充分利用其優點,同時規避其缺點甚至通過自己的方式解決其缺點。


參考資料

  1. Advantages and Disadvantages of a Multithreaded/Multicontexted Application: https://docs.oracle.com/cd/E13203_01/tuxedo/tux71/html/pgthr5.htm
  2. https://www.hostreview.com/blog/160311-the-pros-and-cons-of-using-nodejs: https://www.hostreview.com/blog/160311-the-pros-and-cons-of-using-nodejs
  3. 理解事件循環與任務隊列:https://www.jianshu.com/p/e865c3a7ba10

js事件循環機制辨析