Node.jsのイベントループの仕組みを解析してみる
この記事の内容は、Node.js のイベント ループの仕組みを分析するものです。必要な方は参考にしていただければ幸いです。
イベント ループのメカニズムといくつかの関連概念はブラウザの記事で詳しく紹介されていますが、主にブラウザ側の研究用です。Node 環境でも同じですか?まずデモを見てみましょう。
setTimeout(()=>{ console.log('timer1') Promise.resolve().then(function() { console.log('promise1') })}, 0)setTimeout(()=>{ console.log('timer2') Promise.resolve().then(function() { console.log('promise2') })}, 0)
コンパイルしてブラウザで実行した結果は次のとおりです。すでにご存知のとおりなので、ここでは詳しく説明しません。
timer1 promise1 timer2 promise2
それから、Node の下で実行します。 。 。奇妙なことに、実行結果がブラウザと異なります~
timer1 timer2 promise1 promise2
例は、ブラウザと Node.js のイベント ループのメカニズムが異なることを示しています。見てみましょう~
のイベント処理Node.js
Node.js は、js の解析エンジンとして V8 を使用し、I/O 処理に独自に設計された libuv を使用します。libuv は、いくつかの基盤となる機能をカプセル化するイベント駆動型のクロスプラットフォーム抽象化レイヤーです。さまざまなオペレーティング システムが統合された API を外部に提供します。イベント ループ メカニズムもその中に実装されています。コア ソース コード リファレンス:
int uv_run(uv_loop_t* loop, uv_run_mode mode) { int timeout; int r; int ran_pending; r = uv__loop_alive(loop); if (!r) uv__update_time(loop); while (r != 0 && loop->stop_flag == 0) { uv__update_time(loop); // timers阶段 uv__run_timers(loop); // I/O callbacks阶段 ran_pending = uv__run_pending(loop); // idle阶段 uv__run_idle(loop); // prepare阶段 uv__run_prepare(loop); timeout = 0; if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT) timeout = uv_backend_timeout(loop); // poll阶段 uv__io_poll(loop, timeout); // check阶段 uv__run_check(loop); // close callbacks阶段 uv__run_closing_handles(loop); if (mode == UV_RUN_ONCE) { uv__update_time(loop); uv__run_timers(loop); } r = uv__loop_alive(loop); if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT) break; } if (loop->stop_flag != 0) loop->stop_flag = 0; return r; }
Node.js の公式導入によると、各イベント ループには 6 つのステージが含まれています。以下の図に示すように、libuv ソース コードの実装に対応します。
timers ステージ: このステージはタイマー (setTimeout、setInterval) のコールバックを実行します
I/O コールバック ステージ: ネットワーク通信エラー コールバックなど、いくつかのシステム コール エラーを実行します。
アイドル、準備ステージ: ノードによって内部的にのみ使用されます
ポーリング ステージ: 新しい I を取得します。 /O イベント、適切 条件下では、ノードはここでブロックされます。
チェック ステージ: setImmediate() のコールバックを実行します。
クローズ コールバック ステージ: ソケットのクローズ イベント コールバックを実行します
日常の開発におけるほとんどの非同期タスクはこれら 3 つの段階で処理されるため、タイマーとポーリングに焦点を当てます。これら 3 つの段階を確認してください。
タイマー フェーズ
タイマーはイベント ループ、ノードの最初のフェーズです。 期限切れのタイマーがあるかどうかを確認し、期限切れになっている場合は、そのコールバックをタイマーのタスク キューにプッシュして実行を待ちます。 ノードによるタイマーの有効期限チェックは必ずしも信頼できるものではないため、マシン上で実行されている他のプログラムの影響を受けるか、メインスレッドがアイドル状態ではないため、事前に設定された時間に達したときにタイマーがすぐに実行されるという保証はありません。その時。たとえば、次のコードでは、setTimeout() と setImmediate() の実行順序は不定です。
setTimeout(() => { console.log('timeout') }, 0) setImmediate(() => { console.log('immediate') })
ただし、これらを I/O コールバックに置く場合は、ポーリング フェーズの後にチェック フェーズが続くため、setImmediate() を最初に実行する必要があります。
ポーリング フェーズ
ポーリング フェーズには主に 2 つの機能があります。
ポーリング キュー内のイベントの処理
タイムアウトになったタイマーがある場合、コールバック関数を実行します
偶数ループは、キューが空になるか、実行されたコールバックがシステムの上限 (上限は不明) に達するまで、ポーリング キュー内のコールバックを同期的に実行します。その後、偶数ループは次のことを確認します。プリセット setImmediate() があり、2 つの状況に分けられます。
プリセット setImmediate() がある場合、イベント ループはポーリング フェーズを終了してチェック フェーズに入り、チェックのタスク キューを実行します。フェーズ
プリセット setImmediate() がない場合、イベント ループはブロックされ、この段階で待機します。
詳細については、setImmediate() がイベントを引き起こすことはないことに注意してください。 ループはポーリングフェーズでブロックされるため、以前に設定されたタイマーは実行できなくなりませんか?したがって、投票フェーズのイベントで ループには、タイマー キューが空かどうかをチェックするチェック メカニズムがあり、タイマー キューが空でない場合はイベントが発生します。 ループはイベント ループの次のラウンドを開始します。つまり、タイマー フェーズに再び入ります。
チェック フェーズ
setImmediate() のコールバックがチェック キューに追加されます。イベント ループのフェーズ図から、チェック フェーズの実行順序がポーリングの後にあることがわかります。段階。
概要
イベント ループの各ステージにはタスク キューがあります
イベント ループが特定のステージに到達すると、そのステージのタスク キューはキューが終了するまで実行されます。または、実行されたコールバックが次のステージに進む前にシステムの上限に達します
#すべてのステージが順番に 1 回実行されると、イベント ループはティックを完了したと言われます## #それは意味がありますが、デモがないとまだ完全に理解できません。
const fs = require('fs')fs.readFile('test.txt', () => { console.log('readFile') setTimeout(() => { console.log('timeout') }, 0) setImmediate(() => { console.log('immediate') }) })
実行結果に疑問の余地はありません
readFile immediate timeout
Node.jsとブラウザのイベント ループの違い
前の記事、ブラウザ環境でのマイクロタスク タスクを思い出してください。キューは、各マクロタスクが実行された後に実行されます。
Node.js では、イベント ループのさまざまなステージ間でマイクロタスクが実行されます。つまり、ステージの実行後に、マイクロタスク キュー内のタスクが実行されます。 。
記事の冒頭のデモを確認してください。グローバル スクリプト (main()) が実行されます。 2 つのタイマーが順番にタイマー キューに置かれ、main() が実行され、コール スタックがアイドル状態になり、タスク キューが実行を開始します。
首先进入timers阶段,执行timer1的回调函数,打印timer1,并将promise1.then回调放入microtask队列,同样的步骤执行timer2,打印timer2;
至此,timer阶段执行结束,event loop进入下一个阶段之前,执行microtask队列的所有任务,依次打印promise1、promise2。
对比浏览器端的处理过程:
process.nextTick() VS setImmediate()
In essence, the names should be swapped. process.nextTick() fires more immediately than setImmediate()
来自官方文档有意思的一句话,从语义角度看,setImmediate() 应该比 process.nextTick() 先执行才对,而事实相反,命名是历史原因也很难再变。
process.nextTick() 会在各个事件阶段之间执行,一旦执行,要直到nextTick队列被清空,才会进入到下一个事件阶段,所以如果递归调用 process.nextTick(),会导致出现I/O starving(饥饿)的问题,比如下面例子的readFile已经完成,但它的回调一直无法执行:
const fs = require('fs')const starttime = Date.now()let endtime fs.readFile('text.txt', () => { endtime = Date.now() console.log('finish reading time: ', endtime - starttime)})let index = 0function handler () { if (index++ >= 1000) return console.log(`nextTick ${index}`) process.nextTick(handler) // console.log(`setImmediate ${index}`) // setImmediate(handler)}handler()
process.nextTick()的运行结果:
nextTick 1 nextTick 2 ...... nextTick 999 nextTick 1000 finish reading time: 170
替换成setImmediate(),运行结果:
setImmediate 1 setImmediate 2 finish reading time: 80 ...... setImmediate 999 setImmediate 1000
这是因为嵌套调用的 setImmediate() 回调,被排到了下一次event loop才执行,所以不会出现阻塞。
总结
1、Node.js 的事件循环分为6个阶段
2、浏览器和Node 环境下,microtask 任务队列的执行时机不同
Node.js中,microtask 在事件循环的各个阶段之间执行
浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
3、递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()
以上がNode.jsのイベントループの仕組みを解析してみるの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 はじめに: 技術の継続的な発展により、音声認識技術は人工知能の分野の重要な部分になりました。 WebSocket と JavaScript をベースとしたオンライン音声認識システムは、低遅延、リアルタイム、クロスプラットフォームという特徴があり、広く使用されるソリューションとなっています。この記事では、WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法を紹介します。

WebSocketとJavaScript:リアルタイム監視システムを実現するためのキーテクノロジー はじめに: インターネット技術の急速な発展に伴い、リアルタイム監視システムは様々な分野で広く利用されています。リアルタイム監視を実現するための重要なテクノロジーの 1 つは、WebSocket と JavaScript の組み合わせです。この記事では、リアルタイム監視システムにおける WebSocket と JavaScript のアプリケーションを紹介し、コード例を示し、その実装原理を詳しく説明します。 1.WebSocketテクノロジー

JavaScript と WebSocket を使用してリアルタイム オンライン注文システムを実装する方法の紹介: インターネットの普及とテクノロジーの進歩に伴い、ますます多くのレストランがオンライン注文サービスを提供し始めています。リアルタイムのオンライン注文システムを実装するには、JavaScript と WebSocket テクノロジを使用できます。 WebSocket は、TCP プロトコルをベースとした全二重通信プロトコルで、クライアントとサーバー間のリアルタイム双方向通信を実現します。リアルタイムオンラインオーダーシステムにおいて、ユーザーが料理を選択して注文するとき

WebSocket と JavaScript を使用してオンライン予約システムを実装する方法 今日のデジタル時代では、ますます多くの企業やサービスがオンライン予約機能を提供する必要があります。効率的かつリアルタイムのオンライン予約システムを実装することが重要です。この記事では、WebSocket と JavaScript を使用してオンライン予約システムを実装する方法と、具体的なコード例を紹介します。 1. WebSocket とは何ですか? WebSocket は、単一の TCP 接続における全二重方式です。

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 はじめに: 今日、天気予報の精度は日常生活と意思決定にとって非常に重要です。テクノロジーの発展に伴い、リアルタイムで気象データを取得することで、より正確で信頼性の高い天気予報を提供できるようになりました。この記事では、JavaScript と WebSocket テクノロジを使用して効率的なリアルタイム天気予報システムを構築する方法を学びます。この記事では、具体的なコード例を通じて実装プロセスを説明します。私たちは

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

使用法: JavaScript では、insertBefore() メソッドを使用して、DOM ツリーに新しいノードを挿入します。このメソッドには、挿入される新しいノードと参照ノード (つまり、新しいノードが挿入されるノード) の 2 つのパラメータが必要です。

JavaScript で HTTP ステータス コードを取得する方法の紹介: フロントエンド開発では、バックエンド インターフェイスとの対話を処理する必要があることが多く、HTTP ステータス コードはその非常に重要な部分です。 HTTP ステータス コードを理解して取得すると、インターフェイスから返されたデータをより適切に処理できるようになります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法と、具体的なコード例を紹介します。 1. HTTP ステータス コードとは何ですか? HTTP ステータス コードとは、ブラウザがサーバーへのリクエストを開始したときに、サービスが
