ホームページ > ウェブフロントエンド > jsチュートリアル > Node.js イベント ループの内部: 詳細

Node.js イベント ループの内部: 詳細

Patricia Arquette
リリース: 2025-01-11 20:29:43
オリジナル
957 人が閲覧しました

Inside the Node.js Event Loop: A Deep Dive

Node.js シングルスレッド モデルの探索

Node.js はイベント駆動型の非同期 I/O アプローチを採用し、シングルスレッドで同時実行性の高い JavaScript ランタイム環境を実現します。シングルスレッドは一度に 1 つのことしか実行できないことを意味します。Node.js はどのようにしてたった 1 つのスレッドで高い同時実行性と非同期 I/O を実現するのでしょうか?この記事では、この質問を中心に Node.js のシングルスレッド モデルについて説明します。

高同時実行戦略

一般に、高い同時実行性を実現する解決策は、マルチスレッド モデルを提供することです。サーバーは各クライアント要求に 1 つのスレッドを割り当て、同期 I/O を使用します。システムは、スレッド切り替えを通じて同期 I/O 呼び出しの時間コストを補います。たとえば、Apache はこの戦略を使用します。 I/O 操作には通常時間がかかるため、このアプローチで高いパフォーマンスを達成するのは困難です。ただし、非常にシンプルなので、複雑な対話ロジックを実装できます。

実際、ほとんどの Web サーバー側はあまり計算を実行しません。リクエストを受信した後、そのリクエストを他のサービス (データベースの読み取りなど) に渡し、結果が返されるのを待ち、最後に結果をクライアントに送信します。したがって、Node.js はシングルスレッド モデルを使用してこの状況に対処します。受信リクエストごとにスレッドを割り当てる代わりに、メイン スレッドを使用してすべてのリクエストを処理し、I/O 操作を非同期で処理することで、スレッドの作成、破棄、およびスレッド間の切り替えに伴うオーバーヘッドと複雑さを回避します。

イベントループ

Node.js はメインスレッドでイベント キューを維持します。リクエストを受信すると、そのリクエストはイベントとしてこのキューに追加され、その後も引き続き他のリクエストを受信します。メインスレッドがアイドル状態 (リクエストが受信されていない状態) になると、イベント キューのループを開始して、処理するイベントがあるかどうかを確認します。 2 つのケースがあります。非 I/O タスクの場合、メインスレッドはそれらを直接処理し、コールバック関数を介して上位層に戻ります。 I/O タスクの場合、スレッド プールからスレッドを取得してイベントを処理し、コールバック関数を指定して、キュー内の他のイベントをループし続けます。

スレッド内の I/O タスクが完了すると、指定されたコールバック関数が実行され、完了したイベントがイベント キューの最後に配置され、イベント ループを待ちます。メインスレッドがこのイベントを再びループすると、それを直接処理して上位層に返します。このプロセスはイベント ループと呼ばれ、その動作原理は次の図に示されています。

Inside the Node.js Event Loop: A Deep Dive

この図は、Node.js の全体的な動作原理を示しています。 Node.js は、左から右、上から下の順に、アプリケーション層、V8 エンジン層、Node API 層、LIBUV 層の 4 つの層に分かれています。

  • アプリケーション層: JavaScript インタラクション層です。一般的な例は、http や fs などの Node.js モジュールです。
  • V8 エンジン層: V8 エンジンを使用して JavaScript 構文を解析し、下位層 API と対話します。
  • ノード API 層: 通常は C で実装される上位層モジュールのシステム コールを提供し、オペレーティング システムと対話します。
  • LIBUV レイヤー: イベント ループ、ファイル操作などを実現するクロスプラットフォームの基礎となるカプセル化であり、非同期を実現するための 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 はリクエストの受信にメイン スレッドを 1 つだけ使用していることがわかります。リクエストを受信した後、それらを直接処理せずにイベント キューに入れ、引き続き他のリクエストを受信します。アイドル状態の場合、イベント ループを通じてこれらのイベントを処理し、非同期効果を実現します。もちろん、I/O タスクの場合は、システム レベルでスレッド プールに依存して処理する必要があります。

したがって、Node.js 自体はマルチスレッド プラットフォームですが、単一のスレッドで JavaScript レベルのタスクを処理するということが簡単に理解できます。

CPU を集中的に使用するタスクが欠点である

ここまでで、Node.js のシングルスレッド モデルについて簡単かつ明確に理解できたはずです。イベント駆動型モデルにより、高い同時実行性と非同期 I/O を実現します。ただし、Node.js が苦手な点もあります。

上で述べたように、I/O タスクの場合、Node.js はそれらをスレッド プールに渡して非同期処理を行います。これは効率的かつシンプルです。したがって、Node.js は I/O 集中型のタスクを処理するのに適しています。ただし、すべてのタスクが I/O 集中型であるわけではありません。 CPU を大量に使用するタスク、つまり、データの暗号化と復号化 (node.bcrypt.js)、データの圧縮と解凍 (node-tar) などの CPU 計算のみに依存する操作が発生した場合、Node.js はそれらを 1 つずつ処理します。 1つ。前のタスクが完了していない場合、後続のタスクは待つことしかできません。以下の図に示すように:

Inside the Node.js Event Loop: A Deep Dive

イベントキューでは、前の CPU 計算タスクが完了していないと、後続のタスクがブロックされ、応答が遅くなります。オペレーティング システムがシングルコアの場合は、許容できる可能性があります。しかし、現在、ほとんどのサーバーはマルチ CPU またはマルチコアであり、Node.js には EventLoop が 1 つしかありません。つまり、占有する CPU コアは 1 つだけです。 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 は、平均応答時間 60 ミリ秒で 694 万のリクエストをサポートします。

4. 合理化された開発者エクスペリエンス

  • 直感的な UI でセットアップが簡単です。
  • 完全に自動化された CI/CD パイプラインと GitOps の統合。
  • 実用的な洞察を得るリアルタイムのメトリクスとログ。

5. 容易な拡張性と高性能

  • 自動スケーリングにより、高い同時実行性を簡単に処理できます。
  • 運用上のオーバーヘッドがゼロ - 構築だけに集中できます。

Inside the Node.js Event Loop: A Deep Dive

ドキュメントでさらに詳しく見てみましょう!

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

以上がNode.js イベント ループの内部: 詳細の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート