首頁 web前端 js教程 Node.js 事件循環內部:深入探究

Node.js 事件循環內部:深入探究

Jan 11, 2025 pm 08:29 PM

Inside the Node.js Event Loop: A Deep Dive

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,其運作原理如下圖所示:

Inside the Node.js Event Loop: A Deep Dive

此圖展示了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會一一處理一。如果前面的任務沒有完成,後面的任務就只能等待。如下圖:

Inside the Node.js Event Loop: A Deep Dive

在事件佇列中,如果前面的CPU運算任務沒有完成,後面的任務就會被阻塞,導致反應緩慢。如果作業系統是單核心的話,可能還可以忍受。但現在大多數伺服器都是多CPU或多核心的,而Node.js只有一個EventLoop,也就是說只佔用一個CPU核。當 Node.js 被 CPU 密集型任務佔用,導致其他任務阻塞時,仍有 CPU 核心閒置,造成資源浪費。

所以,Node.js 不適合 CPU 密集型任務。

應用場景

  • RESTful API:請求和回應只需要少量文本,不需要太多邏輯處理。因此,可以並發處理數萬個連接。
  • 聊天服務:輕量級,流量大,沒有複雜的運算邏輯。

Leapcell:用於 Web 託管、非同步任務和 Redis 的下一代無伺服器平台

Inside the Node.js Event Loop: A Deep Dive

最後介紹一下最適合部署Node.js服務的平台:Leapcell。

1. 多語言支持

  • 使用 JavaScript、Python、Go 或 Rust 進行開發。

2.免費部署無限個項目

  • 只需支付使用費用-無請求,不收費。

3. 無與倫比的成本效益

  • 即用即付,無閒置費用。
  • 範例:25 美元支援 694 萬個請求,平均回應時間為 60 毫秒。

4.簡化的開發者體驗

  • 直覺的使用者介面,輕鬆設定。
  • 完全自動化的 CI/CD 管道和 GitOps 整合。
  • 即時指標和日誌記錄以獲取可行的見解。

5. 輕鬆的可擴充性和高效能

  • 自動擴展,輕鬆處理高並發。
  • 零營運開銷-只需專注於建置。

Inside the Node.js Event Loop: A Deep Dive

在文件中探索更多內容!

Leapcell Twitter:https://x.com/LeapcellHQ

以上是Node.js 事件循環內部:深入探究的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

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

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1317
25
PHP教程
1268
29
C# 教程
1248
24
JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

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

JavaScript引擎:比較實施 JavaScript引擎:比較實施 Apr 13, 2025 am 12:05 AM

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

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

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

JavaScript:探索網絡語言的多功能性 JavaScript:探索網絡語言的多功能性 Apr 11, 2025 am 12:01 AM

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

如何使用Next.js(前端集成)構建多租戶SaaS應用程序 如何使用Next.js(前端集成)構建多租戶SaaS應用程序 Apr 11, 2025 am 08:22 AM

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

使用Next.js(後端集成)構建多租戶SaaS應用程序 使用Next.js(後端集成)構建多租戶SaaS應用程序 Apr 11, 2025 am 08:23 AM

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

從C/C到JavaScript:所有工作方式 從C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

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

JavaScript和Web:核心功能和用例 JavaScript和Web:核心功能和用例 Apr 18, 2025 am 12:19 AM

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

See all articles