Node.js知識點
因為javascript引擎的關系,node.js默認是單線程,一個node.js應用無法利用多核資源。不過有第三方庫提供多線程支持,但不是無縫的。node.js是解決I/O瓶頸的(相對於傳統技術,同步阻塞調用浪費線程),它並沒有提高I/O速度,只是資源調度更高效。如果I/O速度不解決,node.js只能說能同時處理好多request,但每個request的響應時間還是那麽長,甚至更長。
Node.js 采用事件驅動和異步 I/O 的方式,實現了一個單線程、高並發的 JavaScript 運行時環境,而單線程就意味著同一時間只能做一件事,那麽 Node.js 如何通過單線程來實現高並發和異步 I/O??
高並發策略
一般來說,高並發的解決方案就是提供多線程模型,服務器為每個客戶端請求分配一個線程,使用同步 I/O,系統通過線程切換來彌補同步 I/O 調用的時間開銷。比如 Apache 就是這種策略,由於 I/O 一般都是耗時操作,因此這種策略很難實現高性能,但非常簡單,可以實現復雜的交互邏輯。
而事實上,大多數網站的服務器端都不會做太多的計算,它們接收到請求以後,把請求交給其它服務來處理(比如讀取數據庫),然後等著結果返回,最後再把結果發給客戶端。因此,Node.js 針對這一事實采用了單線程模型來處理,它不會為每個接入請求分配一個線程,而是用一個主線程處理所有的請求,然後對 I/O 操作進行異步處理,避開了創建、銷毀線程以及在線程間切換所需的開銷和復雜性。
事件循環
Node.js 在主線程裏維護了一個事件隊列,當接到請求後,就將該請求作為一個事件放入這個隊列中,然後繼續接收其他請求。當主線程空閑時(沒有請求接入時),就開始循環事件隊列,檢查隊列中是否有要處理的事件,這時要分兩種情況:如果是非 I/O 任務,就親自處理,並通過回調函數返回到上層調用;如果是 I/O 任務,就從 線程池 中拿出一個線程來處理這個事件,並指定回調函數,然後繼續循環隊列中的其他事件。
當線程中的 I/O 任務完成以後,就執行指定的回調函數,並把這個完成的事件放到事件隊列的尾部,等待事件循環,當主線程再次循環到該事件時,就直接處理並返回給上層調用。 這個過程就叫 事件循環
消息隊列和事件循環
異步過程中,工作線程在異步操作完成後需要通知主線程。那麽這個通知機制是怎樣實現的呢?答案是利用消息隊列和事件循環。工作線程將消息放到消息隊列,主線程通過事件循環過程去取消息。
消息隊列:消息隊列是一個先進先出的隊列,它裏面存放這各種消息。
事件循環:事件循環是指主線程重復從消息隊列中取消息,執行的過程。
實際上,主線程只會做一件事情,就是從消息隊列裏面取消息、執行消息、再取消息、再執行消息。當消息隊列為空時,就會等待直到消息隊列變成非空。而且主線程只有在將前面的消息執行完成後,才會去去下一個消息。這種機制就叫做事件循環機制,取一個消息並執行的過程叫做一次循環。
事件循環機制呢,簡單點來說,就是在執行上下文的過程中,對函數的入棧和出棧。執行前函數先入棧,執行完後函數出棧。如若遇到了一些異步操作像回調函數以及ajax、setTimeout等,會先將他們交給瀏覽器的其他模塊去執行,執行完後,會把回調函數放入到taskqueue中。當所有的call stack執行完後再開始執行task queue中的函數
哪個異步操作完成的早,就排在前面。不論異步操作何時開始執行,只要異步操作執行完成,就可以到消息隊列中排隊
其中setTimeout和Promise的任務隊列叫做macro-task(宏任務),當然如我們所想,還有micro-task(微任務)。
macro-task包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver
參考:
(https://zhuanlan.zhihu.com/p/26229293)
(https://zhuanlan.zhihu.com/p/26238030)
Node.js知識點