Node.js 事件循環內部:深入探究
Node.js 單線程模型探索
Node.js採用事件驅動和非同步I/O的方式,實作了單執行緒、高並發的JavaScript運行環境。既然單執行緒意味著一次只能做一件事,那麼Node.js如何透過一個執行緒實現高並發和非同步I/O呢?本文將圍繞這個問題探討 Node.js 的單線程模型。
高並發策略
一般來說,高並發的解決方案是提供多執行緒模型。伺服器為每個客戶端請求分配一個執行緒並使用同步 I/O。系統透過執行緒切換來彌補同步I/O呼叫的時間成本。例如,Apache就使用這種策略。由於 I/O 操作通常非常耗時,因此這種方法很難獲得高效能。不過,它非常簡單,可以實現複雜的互動邏輯。
事實上,大多數 Web 伺服器端並不執行太多運算。接收到請求後,將請求傳遞給其他服務(例如讀取資料庫),然後等待結果返回,最後將結果傳送給客戶端。因此,Node.js 使用單線程模型來處理這種情況。它不是為每個傳入的請求分配一個線程,而是使用一個主線程來處理所有請求,然後非同步處理 I/O 操作,避免了創建、銷毀線程以及線程之間切換的開銷和複雜性。
事件循環
Node.js 在主執行緒中維護一個事件佇列。當收到請求時,它會作為事件新增到此佇列中,然後繼續接收其他請求。當主執行緒空閒時(沒有請求傳入),它開始循環遍歷事件佇列以檢查是否有事件需要處理。有兩種情況:對於非I/O任務,主執行緒會直接處理,並透過回呼函數傳回上層;對於I/O任務,它會從執行緒池中取出一個執行緒來處理事件,指定一個回呼函數,然後繼續循環佇列中的其他事件。
一旦執行緒中的I/O任務完成,就執行指定的回呼函數,並將完成的事件放在事件佇列的末尾,等待事件循環。當主線程再次循環到這個事件時,直接處理並返回給上層。這個過程稱為Event Loop,其運作原理如下圖所示:
此圖展示了Node.js的整體運作原理。 Node.js 從左到右、從上到下分為四層:應用層、V8 引擎層、Node API 層、LIBUV 層。
- 應用層:是JavaScript交互層。常見的範例是 Node.js 模組,例如 http 和 fs。
- V8引擎層:使用V8引擎解析JavaScript語法,然後與下層API互動。
- Node API層:為上層模組提供系統調用,通常用C實現,與作業系統互動。
- LIBUV Layer:是跨平台的底層封裝,實作事件循環、檔案操作等,是Node.js實作非同步的核心。
無論是Linux平台或Windows平台,Node.js內部都使用執行緒池來完成非同步I/O操作,LIBUV統一了不同平台差異的呼叫。所以,Node.js 中的單執行緒僅意味著 JavaScript 在單執行緒中運行,而不是 Node.js 整體是單線程的。
工作原理
Node.js 實作非同步的核心在於事件。也就是說,它將每個任務視為一個事件,然後透過事件循環來模擬非同步效果。為了更具體、更清楚地理解和接受這個事實,我們下面用偽代碼來描述它的運作方式。
1. 定義事件隊列
由於它是一個佇列,所以它是先進先出(FIFO)的資料結構。我們用JS數組來描述,如下:
/** * Define the event queue * Enqueue: push() * Dequeue: shift() * Empty queue: length === 0 */ let globalEventQueue = [];
我們用陣列來模擬佇列結構:陣列的第一個元素是佇列的頭,最後一個元素是佇列的尾端。 push() 在佇列尾部插入一個元素,shift() 從佇列頭部刪除一個元素。這樣就實作了一個簡單的事件隊列。
2.定義請求接收入口
每個請求都會被攔截並進入處理函數,如下圖:
/** * Receive user requests * Every request will enter this function * Pass parameters request and response */ function processHttpRequest(request, response) { // Define an event object let event = createEvent({ params: request.params, // Pass request parameters result: null, // Store request results callback: function() {} // Specify a callback function }); // Add the event to the end of the queue globalEventQueue.push(event); }
該函數只是將使用者的請求封裝為一個事件,放入佇列中,然後繼續接收其他請求。
3. 定義事件循環
當主執行緒空閒時,開始循環事件佇列。所以我們要定義一個函數來循環事件佇列:
/** * The main body of the event loop, executed by the main thread when appropriate * Loop through the event queue * Handle non-IO tasks * Handle IO tasks * Execute callbacks and return to the upper layer */ function eventLoop() { // If the queue is not empty, continue to loop while (this.globalEventQueue.length > 0) { // Take an event from the head of the queue let event = this.globalEventQueue.shift(); // If it's a time-consuming task if (isIOTask(event)) { // Take a thread from the thread pool let thread = getThreadFromThreadPool(); // Hand it over to the thread to handle thread.handleIOTask(event); } else { // After handling non-time-consuming tasks, directly return the result let result = handleEvent(event); // Finally, return to V8 through the callback function, and then V8 returns to the application event.callback.call(null, result); } } }
主執行緒持續監聽事件佇列。對於I/O任務,它交給線程池處理,對於非I/O任務,它自己處理並返回。
4. 處理I/O任務
執行緒池收到任務後,直接處理I/O操作,例如讀取資料庫:
/** * Define the event queue * Enqueue: push() * Dequeue: shift() * Empty queue: length === 0 */ let globalEventQueue = [];
當I/O任務完成時,執行回調,將請求結果儲存到事件中,並將事件放回佇列中,等待循環。最後,當前線程被釋放。當主執行緒再次循環到該事件時,直接處理。
總結上面的過程,我們發現Node.js只使用一個主執行緒來接收請求。接收到請求後,不要直接處理,而是放入事件佇列中,然後繼續接收其他請求。當它空閒時,它會透過事件循環處理這些事件,從而達到非同步的效果。當然,對於I/O任務,還是需要依賴系統層面的線程池來處理。
因此,我們可以簡單地理解為 Node.js 本身是一個多執行緒平台,但它在單執行緒中處理 JavaScript 層級的任務。
CPU 密集型任務是個缺點
到目前為止,我們應該對 Node.js 的單線程模型有了一個簡單清晰的認識。它透過事件驅動模型實現高並發和非同步I/O。然而,Node.js 也有不擅長的地方。
如上所述,對於I/O任務,Node.js將其交給執行緒池進行非同步處理,高效且簡單。因此,Node.js 適合處理 I/O 密集型任務。但並非所有任務都是 I/O 密集型的。當遇到CPU密集型任務,也就是只依賴CPU計算的操作,如資料加解密(node.bcrypt.js)、資料壓縮解壓縮(node-tar)時,Node.js會一一處理一。如果前面的任務沒有完成,後面的任務就只能等待。如下圖:
在事件佇列中,如果前面的CPU運算任務沒有完成,後面的任務就會被阻塞,導致反應緩慢。如果作業系統是單核心的話,可能還可以忍受。但現在大多數伺服器都是多CPU或多核心的,而Node.js只有一個EventLoop,也就是說只佔用一個CPU核。當 Node.js 被 CPU 密集型任務佔用,導致其他任務阻塞時,仍有 CPU 核心閒置,造成資源浪費。
所以,Node.js 不適合 CPU 密集型任務。
應用場景
- RESTful API:請求和回應只需要少量文本,不需要太多邏輯處理。因此,可以並發處理數萬個連接。
- 聊天服務:輕量級,流量大,沒有複雜的運算邏輯。
Leapcell:用於 Web 託管、非同步任務和 Redis 的下一代無伺服器平台
最後介紹一下最適合部署Node.js服務的平台:Leapcell。
1. 多語言支持
- 使用 JavaScript、Python、Go 或 Rust 進行開發。
2.免費部署無限個項目
- 只需支付使用費用-無請求,不收費。
3. 無與倫比的成本效益
- 即用即付,無閒置費用。
- 範例:25 美元支援 694 萬個請求,平均回應時間為 60 毫秒。
4.簡化的開發者體驗
- 直覺的使用者介面,輕鬆設定。
- 完全自動化的 CI/CD 管道和 GitOps 整合。
- 即時指標和日誌記錄以獲取可行的見解。
5. 輕鬆的可擴充性和高效能
- 自動擴展,輕鬆處理高並發。
- 零營運開銷-只需專注於建置。
在文件中探索更多內容!
Leapcell Twitter:https://x.com/LeapcellHQ
以上是Node.js 事件循環內部:深入探究的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。
