国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

目錄
關鍵要點
什么是事件循環(huán)?
半無限循環(huán)
回調隊列
使用 async/await 的事件循環(huán)
更進一步
事件循環(huán)階段
逐階段演示
線程池
總結
process.nextTick() 與 setImmediate()
結論
關于 Node.js 事件循環(huán)的常見問題
首頁 web前端 js教程 Node.js事件循環(huán):開發(fā)人員的概念和代碼指南

Node.js事件循環(huán):開發(fā)人員的概念和代碼指南

Feb 12, 2025 am 08:36 AM

Node.js 的異步編程:深入理解事件循環(huán)

The Node.js Event Loop: A Developer's Guide to Concepts & Code

異步編程在任何編程語言中都極具挑戰(zhàn)性。并發(fā)、并行和死鎖等概念讓即使是最資深的工程師也感到棘手。異步執(zhí)行的代碼難以預測,出現(xiàn)bug時也難以追蹤。然而,這個問題是不可避免的,因為現(xiàn)代計算擁有多核處理器。每個CPU內核都有其熱限制,單核性能提升已達到瓶頸。這促使開發(fā)者編寫高效的代碼,充分利用硬件資源。

JavaScript 是單線程的,但這是否限制了 Node.js 利用現(xiàn)代架構的能力呢?最大的挑戰(zhàn)之一是處理多線程的固有復雜性。創(chuàng)建新線程和管理線程間的上下文切換代價高昂。操作系統(tǒng)和程序員都需要付出大量努力才能提供一個處理眾多邊緣情況的解決方案。本文將闡述 Node.js 如何通過事件循環(huán)來解決這個難題,深入探討 Node.js 事件循環(huán)的各個方面并演示其工作原理。事件循環(huán)是 Node.js 的殺手級特性之一,因為它以一種全新的方式解決了這個棘手的問題。

關鍵要點

  • Node.js 事件循環(huán)是一個單線程、非阻塞和異步并發(fā)的循環(huán),允許高效處理多個任務,而無需等待每個任務完成。這使得同時處理多個 Web 請求成為可能。
  • 事件循環(huán)是半無限的,這意味著如果調用?;蚧卣{隊列為空,它可以退出。該循環(huán)負責輪詢操作系統(tǒng)以獲取來自傳入連接的回調。
  • 事件循環(huán)在多個階段運行:時間戳更新、循環(huán)活躍性檢查、定時器執(zhí)行、待處理回調執(zhí)行、空閑處理程序執(zhí)行、準備 setImmediate 回調執(zhí)行的句柄、計算輪詢超時、阻塞 I/O、檢查句柄回調執(zhí)行、關閉回調執(zhí)行以及迭代結束。
  • Node.js 利用兩個主要部分:V8 JavaScript 引擎和 libuv。網(wǎng)絡 I/O、文件 I/O 和 DNS 查詢通過 libuv 進行。線程池中可用于這些任務的線程數(shù)量有限,可以通過 UV_THREADPOOL_SIZE 環(huán)境變量進行設置。
  • 在每個階段結束時,循環(huán)執(zhí)行 process.nextTick() 回調,它不是事件循環(huán)的一部分,因為它在每個階段結束時運行。setImmediate() 回調是整個事件循環(huán)的一部分,因此它并不像名稱暗示的那樣立即執(zhí)行。一般建議使用 setImmediate()。

什么是事件循環(huán)?

事件循環(huán)是一個單線程、非阻塞和異步并發(fā)的循環(huán)。對于沒有計算機科學學位的人來說,想象一下一個執(zhí)行數(shù)據(jù)庫查找的 Web 請求。單線程一次只能執(zhí)行一項操作。它不會等待數(shù)據(jù)庫響應,而是繼續(xù)處理隊列中的其他任務。在事件循環(huán)中,主循環(huán)展開調用棧,并且不等待回調。由于循環(huán)不會阻塞,因此它可以同時處理多個 Web 請求。多個請求可以同時排隊,使其具有并發(fā)性。循環(huán)不會等待一個請求的所有操作都完成,而是根據(jù)回調的出現(xiàn)順序進行處理,而不會阻塞。

循環(huán)本身是半無限的,這意味著如果調用?;蚧卣{隊列為空,它可以退出循環(huán)??梢詫⒄{用棧視為同步代碼,例如 console.log,在循環(huán)輪詢更多工作之前展開。Node.js 使用底層的 libuv 來輪詢操作系統(tǒng)以獲取來自傳入連接的回調。

您可能想知道,為什么事件循環(huán)在單線程中執(zhí)行?對于每個連接所需的數(shù)據(jù)而言,線程在內存中相對較重。線程是操作系統(tǒng)資源,需要啟動,這無法擴展到數(shù)千個活動連接。

通常情況下,多線程也會使情況復雜化。如果回調返回數(shù)據(jù),它必須將上下文編組回正在執(zhí)行的線程。線程間的上下文切換速度很慢,因為它必須同步當前狀態(tài),例如調用?;蚓植孔兞?。事件循環(huán)在多個線程共享資源時可以避免bug,因為它單線程。單線程循環(huán)減少了線程安全邊緣情況,并且可以更快地進行上下文切換。這就是循環(huán)背后的真正天才之處。它在保持可擴展性的同時有效地利用了連接和線程。

理論足夠了;現(xiàn)在來看看代碼是什么樣的。您可以隨意在 REPL 中進行操作或下載源代碼。

半無限循環(huán)

事件循環(huán)必須回答的最大問題是循環(huán)是否處于活動狀態(tài)。如果是,則確定在回調隊列上等待多長時間。在每次迭代中,循環(huán)展開調用棧,然后進行輪詢。

這是一個阻塞主循環(huán)的示例:

setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循環(huán)活動這么長時間

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}

如果您運行此代碼,請注意循環(huán)被阻塞了兩秒鐘。但是,循環(huán)會保持活動狀態(tài),直到回調在五秒鐘后執(zhí)行。一旦主循環(huán)解除阻塞,輪詢機制就會確定它在回調上等待多長時間。當調用棧展開并且沒有剩余回調時,此循環(huán)結束。

回調隊列

現(xiàn)在,當我阻塞主循環(huán)然后調度回調時會發(fā)生什么?一旦循環(huán)被阻塞,它就不會將更多回調添加到隊列中:

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
// 這需要 7 秒才能執(zhí)行
setTimeout(() => console.log('Ran callback A'), 5000);

這次循環(huán)保持活動狀態(tài)七秒鐘。事件循環(huán)在其簡單性方面是愚蠢的。它無法知道將來可能會排隊什么。在實際系統(tǒng)中,傳入的回調會排隊并在主循環(huán)可以進行輪詢時執(zhí)行。事件循環(huán)在解除阻塞時會順序地經歷幾個階段。因此,為了在關于循環(huán)的面試中脫穎而出,請避免使用“事件發(fā)射器”或“反應器模式”等花哨的術語。它是一個簡單的單線程循環(huán),并發(fā)且非阻塞。

使用 async/await 的事件循環(huán)

為了避免阻塞主循環(huán),一個想法是用 async/await 包裝同步 I/O:

const fs = require('fs');
const readFileSync = async (path) => await fs.readFileSync(path);

readFileSync('readme.md').then((data) => console.log(data));
console.log('The event loop continues without blocking...');

await 之后出現(xiàn)的任何內容都來自回調隊列。代碼看起來像同步阻塞代碼,但它不會阻塞。請注意,async/await 使 readFileSync 成為可 then 的,這將其從主循環(huán)中移除。可以將 await 之后出現(xiàn)的任何內容視為通過回調進行的非阻塞操作。

完全披露:以上代碼僅用于演示目的。在實際代碼中,我建議使用 fs.readFile,它會觸發(fā)一個可以圍繞 Promise 包裝的回調??傮w意圖仍然有效,因為這將阻塞 I/O 從主循環(huán)中移除。

更進一步

如果我告訴你事件循環(huán)不僅僅是調用棧和回調隊列呢?如果事件循環(huán)不僅僅是一個循環(huán),而是多個循環(huán)呢?如果它可以在底層擁有多個線程呢?

現(xiàn)在,我想帶你深入 Node.js 內部。

事件循環(huán)階段

這些是事件循環(huán)階段:

The Node.js Event Loop: A Developer's Guide to Concepts & Code

圖片源:libuv 文檔

  1. 更新時間戳。事件循環(huán)在循環(huán)開始時緩存當前時間,以避免頻繁進行與時間相關的系統(tǒng)調用。這些系統(tǒng)調用是 libuv 的內部調用。
  2. 循環(huán)是否處于活動狀態(tài)?如果循環(huán)具有活動句柄、活動請求或正在關閉的句柄,則它處于活動狀態(tài)。如所示,隊列中的待處理回調使循環(huán)保持活動狀態(tài)。
  3. 執(zhí)行到期的定時器。這是 setTimeout 或 setInterval 回調運行的地方。循環(huán)檢查緩存的now 以使到期的活動回調執(zhí)行。
  4. 執(zhí)行隊列中的待處理回調。如果之前的迭代延遲了任何回調,則這些回調會在此時運行。輪詢通常會立即運行 I/O 回調,但也有例外。此步驟處理來自上一次迭代的任何滯后回調。
  5. 執(zhí)行空閑處理程序——主要是因為命名不當,因為這些處理程序在每次迭代中都會運行,并且是 libuv 的內部處理程序。
  6. 準備在循環(huán)迭代中執(zhí)行 setImmediate 回調的句柄。這些句柄在循環(huán)阻塞 I/O 之前運行,并為這種回調類型準備隊列。
  7. 計算輪詢超時。循環(huán)必須知道它阻塞 I/O 的時間。這就是它如何計算超時的:
    • 如果循環(huán)即將退出,則超時為 0。
    • 如果沒有活動句柄或請求,則超時為 0。
    • 如果有任何空閑句柄,則超時為 0。
    • 如果隊列中有任何待處理的句柄,則超時為 0。
    • 如果有任何正在關閉的句柄,則超時為 0。
    • 如果以上都不是,則超時設置為最接近的定時器,如果沒有任何活動定時器,則為無限。
  8. 循環(huán)使用上一個階段的持續(xù)時間阻塞 I/O。隊列中的與 I/O 相關的回調在此處執(zhí)行。
  9. 執(zhí)行檢查句柄回調。此階段是 setImmediate 運行的階段,它是準備句柄的對應階段。在 I/O 回調執(zhí)行過程中排隊的任何 setImmediate 回調都會在此處運行。
  10. 執(zhí)行關閉回調。這些是從已關閉連接中釋放的活動句柄。
  11. 迭代結束。

您可能想知道為什么輪詢在應該是非阻塞的情況下會阻塞 I/O?只有當隊列中沒有待處理的回調并且調用棧為空時,循環(huán)才會阻塞。在 Node.js 中,最接近的定時器可以通過 setTimeout 設置,例如。如果設置為無限大,則循環(huán)將等待傳入連接以進行更多工作。這是一個半無限循環(huán),因為當沒有剩余工作并且存在活動連接時,輪詢會使循環(huán)保持活動狀態(tài)。

以下是此超時計算的 Unix 版本,以其全部 C 代碼形式:

setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循環(huán)活動這么長時間

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}

您可能不太熟悉 C 語言,但這讀起來像英語,并且完全按照第七階段所述執(zhí)行。

逐階段演示

為了用純 JavaScript 顯示每個階段:

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
// 這需要 7 秒才能執(zhí)行
setTimeout(() => console.log('Ran callback A'), 5000);

因為文件 I/O 回調在階段四和階段九之前運行,所以預計 setImmediate() 會先觸發(fā):

const fs = require('fs');
const readFileSync = async (path) => await fs.readFileSync(path);

readFileSync('readme.md').then((data) => console.log(data));
console.log('The event loop continues without blocking...');

沒有 DNS 查詢的網(wǎng)絡 I/O 比文件 I/O 成本更低,因為它在主事件循環(huán)中執(zhí)行。文件 I/O 通過線程池排隊。DNS 查詢也使用線程池,因此這使得網(wǎng)絡 I/O 與文件 I/O 一樣昂貴。

線程池

Node.js 內部有兩個主要部分:V8 JavaScript 引擎和 libuv。文件 I/O、DNS 查詢和網(wǎng)絡 I/O 通過 libuv 進行。

這是整體架構:

The Node.js Event Loop: A Developer's Guide to Concepts & Code

圖片源:libuv 文檔

對于網(wǎng)絡 I/O,事件循環(huán)在主線程內進行輪詢。此線程不是線程安全的,因為它不會與另一個線程進行上下文切換。文件 I/O 和 DNS 查詢是特定于平臺的,因此方法是在線程池中運行它們。一個想法是自己進行 DNS 查詢以避免進入線程池,如上面的代碼所示。例如,輸入 IP 地址而不是 localhost 會將查找從池中移除。線程池中可用的線程數(shù)量有限,可以通過 UV_THREADPOOL_SIZE 環(huán)境變量進行設置。默認線程池大小約為四個。

V8 在單獨的循環(huán)中執(zhí)行,清空調用棧,然后將控制權返回給事件循環(huán)。V8 可以使用多個線程進行其自身循環(huán)之外的垃圾回收??梢詫?V8 視為一個引擎,它接收原始 JavaScript 并將其在硬件上運行。

對于普通程序員來說,JavaScript 保持單線程,因為沒有線程安全問題。V8 和 libuv 內部會啟動它們自己單獨的線程以滿足它們自己的需求。

如果 Node.js 中存在吞吐量問題,請從主事件循環(huán)開始。檢查應用程序完成單個迭代需要多長時間。它不應超過一百毫秒。然后,檢查線程池饑餓以及可以從池中驅逐的內容。也可以通過環(huán)境變量增加池的大小。最后一步是在同步執(zhí)行的 V8 中對 JavaScript 代碼進行微基準測試。

總結

事件循環(huán)繼續(xù)迭代每個階段,因為回調被排隊。但是,在每個階段內,都有方法可以排隊另一種類型的回調。

process.nextTick() 與 setImmediate()

在每個階段結束時,循環(huán)執(zhí)行 process.nextTick() 回調。請注意,此回調類型不是事件循環(huán)的一部分,因為它在每個階段結束時運行。setImmediate() 回調是整個事件循環(huán)的一部分,因此它并不像名稱暗示的那樣立即執(zhí)行。由于 process.nextTick() 需要了解事件循環(huán)的內部機制,因此我通常建議使用 setImmediate()。

您可能需要 process.nextTick() 的幾個原因:

  1. 允許網(wǎng)絡 I/O 在循環(huán)繼續(xù)之前處理錯誤、清理或重試請求。
  2. 可能需要在調用棧展開后但在循環(huán)繼續(xù)之前運行回調。

例如,事件發(fā)射器希望在其自身構造函數(shù)中觸發(fā)事件。調用棧必須先展開才能調用事件。

setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循環(huán)活動這么長時間

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}

允許調用棧展開可以防止諸如 RangeError: Maximum call stack size exceeded 之類的錯誤。一個需要注意的是確保 process.nextTick() 不會阻塞事件循環(huán)。在同一階段內遞歸回調調用可能會導致阻塞問題。

結論

事件循環(huán)在其終極復雜性中體現(xiàn)了簡單性。它解決了一個難題,例如異步性、線程安全性和并發(fā)性。它刪除了無用或不需要的部分,并以最有效的方式最大限度地提高了吞吐量。因此,Node.js 程序員可以減少追逐異步錯誤的時間,而將更多時間用于交付新功能。

關于 Node.js 事件循環(huán)的常見問題

什么是 Node.js 事件循環(huán)?Node.js 事件循環(huán)是允許 Node.js 執(zhí)行非阻塞異步操作的核心機制。它負責在單線程事件驅動環(huán)境中處理 I/O 操作、定時器和回調。

Node 事件循環(huán)是如何工作的?事件循環(huán)不斷檢查事件隊列中是否有待處理的事件或回調,并按添加順序執(zhí)行它們。它在一個循環(huán)中運行,根據(jù)事件的可用性處理事件,這使得 Node.js 中的異步編程成為可能。

事件循環(huán)在 Node.js 應用程序中的作用是什么?事件循環(huán)是 Node.js 的核心,它確保應用程序保持響應能力,并且可以處理許多同時連接,而無需多線程。

Node.js 事件循環(huán)的階段有哪些?Node.js 中的事件循環(huán)有幾個階段,包括定時器、待處理回調、空閑、輪詢、檢查和關閉。這些階段決定了事件的處理方式和順序。

事件循環(huán)處理的最常見事件類型有哪些?常見的事件包括 I/O 操作(例如,從文件讀取或發(fā)出網(wǎng)絡請求)、定時器(例如,setTimeout 和 setInterval)和回調函數(shù)(例如,來自異步操作的回調)。

Node 如何在事件循環(huán)中處理長時間運行的操作?長時間運行的 CPU 密集型操作可能會阻塞事件循環(huán),應使用 child_process 或 worker_threads 模塊等模塊將其卸載到子進程或工作線程中。

調用棧和事件循環(huán)有什么區(qū)別?調用棧是一個數(shù)據(jù)結構,用于跟蹤當前執(zhí)行上下文中的函數(shù)調用,而事件循環(huán)負責管理異步和非阻塞操作。它們協(xié)同工作,因為事件循環(huán)調度回調和 I/O 操作的執(zhí)行,然后將它們推送到調用棧中。

事件循環(huán)中的“tick”是什么?“tick”指的是事件循環(huán)的單個迭代。在每次 tick 中,事件循環(huán)都會檢查是否有待處理的事件,并執(zhí)行任何準備運行的回調。Ticks 是 Node.js 應用程序中的基本工作單元。

以上是Node.js事件循環(huán):開發(fā)人員的概念和代碼指南的詳細內容。更多信息請關注PHP中文網(wǎng)其他相關文章!

本站聲明
本文內容由網(wǎng)友自發(fā)貢獻,版權歸原作者所有,本站不承擔相應法律責任。如您發(fā)現(xiàn)有涉嫌抄襲侵權的內容,請聯(lián)系admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣服圖片

Undresser.AI Undress

Undresser.AI Undress

人工智能驅動的應用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用于從照片中去除衣服的在線人工智能工具。

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集成開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級代碼編輯軟件(SublimeText3)

Java vs. JavaScript:清除混亂 Java vs. JavaScript:清除混亂 Jun 20, 2025 am 12:27 AM

Java和JavaScript是不同的編程語言,各自適用于不同的應用場景。Java用于大型企業(yè)和移動應用開發(fā),而JavaScript主要用于網(wǎng)頁開發(fā)。

JavaScript評論:簡短說明 JavaScript評論:簡短說明 Jun 19, 2025 am 12:40 AM

JavascriptconcommentsenceenceEncorenceEnterential gransimenting,reading and guidingCodeeXecution.1)單inecommentsareusedforquickexplanations.2)多l(xiāng)inecommentsexplaincomplexlogicorprovideDocumentation.3)

如何在JS中與日期和時間合作? 如何在JS中與日期和時間合作? Jul 01, 2025 am 01:27 AM

JavaScript中的日期和時間處理需注意以下幾點:1.創(chuàng)建Date對象有多種方式,推薦使用ISO格式字符串以保證兼容性;2.獲取和設置時間信息可用get和set方法,注意月份從0開始;3.手動格式化日期需拼接字符串,也可使用第三方庫;4.處理時區(qū)問題建議使用支持時區(qū)的庫,如Luxon。掌握這些要點能有效避免常見錯誤。

為什么要將標簽放在的底部? 為什么要將標簽放在的底部? Jul 02, 2025 am 01:22 AM

PlacingtagsatthebottomofablogpostorwebpageservespracticalpurposesforSEO,userexperience,anddesign.1.IthelpswithSEObyallowingsearchenginestoaccesskeyword-relevanttagswithoutclutteringthemaincontent.2.Itimprovesuserexperiencebykeepingthefocusonthearticl

JavaScript與Java:開發(fā)人員的全面比較 JavaScript與Java:開發(fā)人員的全面比較 Jun 20, 2025 am 12:21 AM

JavaScriptIspreferredforredforwebdevelverment,而Javaisbetterforlarge-ScalebackendsystystemsandSandAndRoidApps.1)JavascriptexcelcelsincreatingInteractiveWebexperienceswebexperienceswithitswithitsdynamicnnamicnnamicnnamicnnamicnemicnemicnemicnemicnemicnemicnemicnemicnddommanipulation.2)

JavaScript:探索用于高效編碼的數(shù)據(jù)類型 JavaScript:探索用于高效編碼的數(shù)據(jù)類型 Jun 20, 2025 am 12:46 AM

javascripthassevenfundaMentalDatatypes:數(shù)字,弦,布爾值,未定義,null,object和symbol.1)numberSeadUble-eaduble-ecisionFormat,forwidevaluerangesbutbecautious.2)

什么是在DOM中冒泡和捕獲的事件? 什么是在DOM中冒泡和捕獲的事件? Jul 02, 2025 am 01:19 AM

事件捕獲和冒泡是DOM中事件傳播的兩個階段,捕獲是從頂層向下到目標元素,冒泡是從目標元素向上傳播到頂層。1.事件捕獲通過addEventListener的useCapture參數(shù)設為true實現(xiàn);2.事件冒泡是默認行為,useCapture設為false或省略;3.可使用event.stopPropagation()阻止事件傳播;4.冒泡支持事件委托,提高動態(tài)內容處理效率;5.捕獲可用于提前攔截事件,如日志記錄或錯誤處理。了解這兩個階段有助于精確控制JavaScript響應用戶操作的時機和方式。

Java和JavaScript有什么區(qū)別? Java和JavaScript有什么區(qū)別? Jun 17, 2025 am 09:17 AM

Java和JavaScript是不同的編程語言。1.Java是靜態(tài)類型、編譯型語言,適用于企業(yè)應用和大型系統(tǒng)。2.JavaScript是動態(tài)類型、解釋型語言,主要用于網(wǎng)頁交互和前端開發(fā)。

See all articles