ホームページ > ウェブフロントエンド > jsチュートリアル > node.jsイベントループ:概念とコードの開発者のガイド

node.jsイベントループ:概念とコードの開発者のガイド

Christopher Nolan
リリース: 2025-02-12 08:36:12
オリジナル
573 人が閲覧しました

Node.jsの非同期プログラミング:イベントループの詳細な理解

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

非同期プログラミングは、どのプログラミング言語でも非常に困難です。並行性、並列性、デッドロックなどの概念は、最も経験豊富なエンジニアでさえトリッキーになります。非同期に実行されたコードは、予測が困難であり、バグがある場合に追跡するのが困難です。ただし、最新のコンピューティングにはマルチコアプロセッサがあるため、この問題は避けられません。各CPUコアには独自の熱制限があり、シングルコアのパフォーマンス改善がボトルネックに達しました。これにより、開発者は効率的なコードを作成し、ハードウェアリソースを最大限に活用するようになります。

javascriptはシングルスレッドですが、これにより、node.jsが最新のアーキテクチャを活用する能力が制限されますか?最大の課題の1つは、マルチスレッドの固有の複雑さに対処することです。新しいスレッドを作成し、スレッド間のコンテキストの切り替えを管理するのは高価です。オペレーティングシステムとプログラマーの両方が、多数のエッジケースを処理するソリューションを提供するために多くの努力が必要です。この記事では、node.jsがイベントループを介してこの問題を解決する方法を説明し、node.jsイベントループのさまざまな側面を調査し、それがどのように機能するかを示します。イベントループは、Node.jsのキラー機能の1つです。これは、まったく新しい方法でこのトリッキーな問題を解決するためです。

キーポイント

  • node.jsイベントループは、各タスクが完了するのを待たずに複数のタスクを効率的に処理できるようにする、単一スレッド、非ブロッキング、非同期の同時ループです。これにより、複数のWebリクエストを同時に処理できます。
  • イベントループはセミインフィニットです。つまり、コールスタックまたはコールバックキューが空である場合に終了できます。このループは、着信接続からコールバックを取得するためにオペレーティングシステムに投票する責任があります。
  • イベントループは複数の段階で実行されます:タイムスタンプの更新、ループアクティビティチェック、タイマーの実行、保留中のコールバック実行、アイドルハンドラーの実行、セットメディートコールバック実行のためのハンドルの準備、ポーリングタイムアウトの計算、I/Oのブロック、コールバックの実行、チェックハンドル、クローズコールバックの実行、および繰り返しの終了。
  • node.jsは、V8 JavaScriptエンジンとLibuvの2つの主要な部分を利用しています。ネットワークI/O、ファイルI/O、およびDNSクエリは、LIBUVを介して実行されます。スレッドプール内のこれらのタスクで利用可能なスレッドの数は限られており、uv_threadpool_size環境変数を介して設定できます。
  • 各段階の終わりに、ループはプロセスを実行します。nexttick()コールバックは、各段階の終わりに実行されるため、イベントループの一部ではありません。 Setimmediate()コールバックはイベントループ全体の一部であるため、名前が示すようにすぐに実行されません。通常、Setimmediate()を使用することをお勧めします。
イベントループとは何ですか?

イベントループは、シングルスレッド、非ブロッキング、非同期の同時ループです。コンピューターサイエンスの学位のない人の場合、データベースの検索を実行するWebリクエストを想像してください。単一のスレッドは、一度に1つの操作のみを実行できます。データベースが応答するのを待つ代わりに、キュー内の他のタスクを処理し続けます。イベントループでは、メインループがコールスタックを展開し、コールバックを待ちません。ループはブロックされないため、複数のWeb要求を同時に処理できます。複数のリクエストを同時にキューにすることができ、それらを同時にすることができます。ループは、要求のすべての操作が完了するのを待つのではなく、ブロックせずにコールバックが発生する順序に従って処理されます。

ループ自体は半無効です。つまり、コールスタックまたはコールバックキューが空である場合、ループを終了できます。コールスタックは、Console.logなどの同期コードと見なすことができます。 node.jsは、下にあるLibuvを使用して、着信接続からのコールバックのオペレーティングシステムを投票します。

イベントループが単一のスレッドで実行される理由は疑問に思うかもしれません。スレッドは、各接続に必要なデータのメモリが比較的重いです。スレッドは、開始する必要があるオペレーティングシステムリソースであり、数千のアクティブな接続に拡張することはできません。

通常、マルチスレッドは状況を複雑にする可能性があります。コールバックがデータを返す場合、実行中のスレッドにコンテキストを元に戻す必要があります。コールスタックやローカル変数など、現在の状態を同期する必要があるため、スレッド間のコンテキストの切り替えは遅くなります。イベントループは、単一のスレッドであるため、複数のスレッドがリソースを共有する場合、バグを回避できます。シングルスレッドループは、スレッドセーフエッジケースを減らし、より高速なコンテキストスイッチングを可能にします。これは、ループの背後にある本当の天才です。スケーラビリティを維持しながら、接続とスレッドを効果的に利用します。

理論では、コードがどのように見えるかを見てみましょう。好きなようにREPLで実行するか、ソースコードをダウンロードできます。

半inteLoop

イベントループが答えなければならない最大の質問は、ループがアクティブであるかどうかです。その場合は、コールバックキューを待つ時間を決定します。各反復で、ループはコールスタックとポーリングを拡張します。

これはメインループをブロックする例です:

setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循环活动这么长时间

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
このコードを実行する場合は、ループが2秒間ブロックされていることに注意してください。ただし、5秒後にコールバックが実行されるまでループはアクティブのままです。メインループがブロックされていないと、ポーリングメカニズムがコールバックを待つ時間を決定します。このループは、コールスタックが展開すると終了し、コールバックが残っていません。

コールバックキュー

さて、メインループをブロックしてからコールバックをスケジュールするとどうなりますか?ループがブロックされたら、キューにコールバックを追加することはありません:

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
// 这需要 7 秒才能执行
setTimeout(() => console.log('Ran callback A'), 5000);
ログイン後にコピー
ログイン後にコピー
このサイクルは7秒間アクティブなままです。イベントループは、シンプルさの点で愚かです。将来何が列に並んでいるのかを知る方法はありません。実際のシステムでは、メインループをポーリングできるときに、着信コールバックがキューに登録され、実行されます。イベントループは、ブロック解除時にいくつかの段階を順番に通過します。そのため、ループに関するインタビューで目立つように、「イベントランチャー」や「リアクターモード」などの派手な用語を避けてください。シンプルなシングルスレッドループ、同時および非ブロッキングです。

ASYNC/WAITを使用したイベントループ メインループのブロックを避けるために、1つのアイデアは、同期I/Oを非同期/async/awaitで巻くことです。

待ち望んだ後に表示されるものはすべて、コールバックキューから来ます。コードは同期ブロッキングコードのように見えますが、ブロックしません。 Async/async/awaitは、メインループから削除するReadFilesYnc A

のその後に

になることに注意してください。待っている後に表示されるものはすべて、コールバックを介して非ブロッキング操作と見なすことができます。

完全な開示:上記のコードは、デモンストレーションのみを目的としています。実際のコードでは、fs.readfileを使用することをお勧めします。これは、約束に巻き込まれるコールバックをトリガーすることをお勧めします。これにより、メインループからの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...');
ログイン後にコピー
ログイン後にコピー

さらに一歩進んでください イベントループは、コールスタックやコールバックキューだけではないことをお知らせした場合はどうなりますか?イベントループが1つのループだけでなく、複数のループである場合はどうなりますか?下部に複数のスレッドがある場合はどうなりますか?

さて、私はあなたをnode.jsに深く連れて行きたいです

イベントループステージ

これらは、イベントループフェーズです:

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

画像出典:libuv document

  1. タイムスタンプを更新します。イベントループは、頻繁な時間関連システム呼び出しを避けるために、ループの開始時に現在の時間をキャッシュします。これらのシステム呼び出しは、Libuvへの内部呼び出しです。
  2. ループはアクティブですか?ループにアクティブなハンドル、アクティブなリクエスト、または閉じたハンドルがある場合、アクティブです。示されているように、キュー内の保留中のコールバックは、ループをアクティブに保ちます。
  3. 有効期限タイマーを実行します。これは、SettimeOutまたはSetIntervalのコールバックが実行される場所です。 cachedNowを確認するためのループは、期限切れのアクティブコールバックを実行できるようにします。
  4. キューで保留中のコールバックを実行します。以前の反復によりコールバックが遅れた場合、これらのコールバックはこの時点で実行されます。ポーリングは通常、例外を除いて、すぐにI/Oコールバックを実行します。このステップは、最後の反復から遅れたコールバックを処理します。
  5. アイドルハンドラーを実行します。主に、これらのハンドラーがすべての反復で実行され、Libuvの内部ハンドラーであるため、主に不適切な命名のためです。
  6. ループイテレーションでセットメディートコールバックにハンドルを実行する準備をします。これらのハンドルは、ループがI/Oをブロックする前に実行され、このコールバックタイプのキューを準備します。
  7. ポーリングタイムアウトを計算します。ループは、いつI/Oをブロックするかを知る必要があります。これは、タイムアウトの計算方法です:
    • ループが終了しようとしている場合、タイムアウトは0です。
    • アクティブなハンドルまたはリクエストがない場合、タイムアウトは0です。
    • フリーハンドルがある場合、タイムアウトは0です。
    • キューに保留中のハンドルがある場合、タイムアウトは0です。
    • 閉じているハンドルがある場合、タイムアウトは0です。
    • 上記のいずれもない場合、タイムアウトは最も近いタイマーに設定され、アクティブなタイマーがない場合、
    • infiniteです。
    前のステージブロックI/Oの期間をサイクリングします。キュー内のI/O関連のコールバックはここで実行されます。
  8. チェックハンドルコールバックを実行します。この段階は、ハンドルを準備するための対応する段階であるSetimmediate実行の段階です。 i/oコールバック実行中にキューに掲載されたSetimmediateコールバックは、ここで実行されます。
  9. クローズドコールバックを実行します。これらは、閉じた接続から解放されたアクティブなハンドルです。
  10. 反復は終了します。
  11. ノンブロッキングが必要なのに、なぜポーリングがI/Oをブロックするのか疑問に思うかもしれません。キューに保留中のコールバックがなく、コールスタックが空になっている場合にのみ、ループがブロックされます。 node.jsでは、最も近いタイマーは、たとえばSettimeoutを介して設定できます。無限に設定すると、ループは着信接続がより多くの作業を行うのを待ちます。これは、ポーリングが残りの作業がなく、アクティブな接続がある場合にループをアクティブに保つため、半無効ループです。

以下は、このタイムアウト計算のUNIXバージョンであり、Cコードフォーム全体:

setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循环活动这么长时间

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

Cにはあまり精通していないかもしれませんが、これは英語のように読み、フェーズ7で説明されているとおりです。

段階ごとのデモンストレーション

純粋なjavaScriptで各ステージを表示するには:

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
// 这需要 7 秒才能执行
setTimeout(() => console.log('Ran callback A'), 5000);
ログイン後にコピー
ログイン後にコピー

ファイルI/Oコールバックはステージ4および9の前に実行されるため、SetimMediate()が最初に発射されることが予想されます。

DNSクエリのないネットワークI/Oは、メインイベントループで実行されるため、ファイルI/Oよりも安価です。ファイルI/Oは、スレッドプールを介してキューに掲載されています。 DNSクエリもスレッドプールを使用するため、ネットワークI/Oはファイル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...');
ログイン後にコピー
ログイン後にコピー

スレッドプール

node.jsには、V8 JavaScriptエンジンとLibuvの2つの主要な部分があります。ファイルI/O、DNSクエリ、およびネットワークI/Oは、LIBUVを介して実行されます。

これは全体的な構造です:

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

画像出典:libuv document

ネットワークI/Oの場合、メインスレッド内のイベントループポーリング。このスレッドは、別のスレッドとのコンテキストスイッチがないため、スレッドセーフではありません。ファイルI/OおよびDNSクエリはプラットフォーム固有であるため、方法はスレッドプールでそれらを実行することです。 1つのアイデアは、上記のコードに示すように、スレッドプールに入ることを避けるためにDNSのクエリを実行することです。たとえば、LocalHostの代わりにIPアドレスを入力すると、プールからルックアップが削除されます。スレッドプールで利用可能なスレッドの数は限られており、uv_threadpool_size環境変数を介して設定できます。デフォルトのスレッドプールサイズは約4です。

V8は別のループで実行され、コールスタックをクリアし、コントロールをイベントループに戻します。 V8は、独自のループの外側のガベージコレクションに複数のスレッドを使用できます。 V8は、元のJavaScriptを使用してハードウェアで実行するエンジンと考えてください。

通常のプログラマーの場合、JavaScriptはスレッドの安全性の問題がないため、単一のスレッドのままです。 V8とLibuvは、独自のニーズを満たすために、独自のスレッドを内部的に開始します。

node.jsにスループットの問題がある場合は、メインイベントループから始めます。アプリケーションが単一の反復を完了するのにかかる時間を確認してください。 100ミリ秒を超えてはなりません。次に、スレッドプールの飢erとプールから追い出すことができるものを確認します。プールのサイズは、環境変数によって増加することもできます。最後のステップは、同期して実行されたV8でJavaScriptコードのマイクロベンチマークを実行することです。

要約

コールバックがキューになっているため、イベントループは各段階を繰り返し続けます。ただし、各段階では、別のタイプのコールバックをキューする方法があります。

process.nexttick()およびsetimmediate()

各段階の終わりに、process.nexttick()コールバックがループで実行されます。このコールバックタイプは、各段階の終わりに実行されるため、イベントループの一部ではないことに注意してください。 Setimmediate()コールバックはイベントループ全体の一部であるため、名前が示すようにすぐに実行されません。 process.nexttick()にはイベントループの内部メカニズムを理解する必要があるため、通常、setimmediate()を使用することをお勧めします。

process.nexttick():

が必要ないくつかの理由
  1. ループが続く前に、ネットワークI/Oがエラーを処理したり、クリーンアップしたり、リクエストを再試行したりすることを許可します。
  2. コールスタックが展開された後、ループが継続する前にコールバックを実行する必要がある場合があります。
たとえば、イベントトランスミッターは、独自のコンストラクターでイベントをトリガーしたいと考えています。イベントを呼び出す前に、コールスタックを拡張する必要があります。

setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循环活动这么长时间

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
通話スタックの拡張を許可すると、raveneRROR:最大コールスタックサイズなどのエラーが防止されます。注意すべきことの1つは、process.nexttick()がイベントループをブロックしないことを確認することです。同じ段階内の再帰コールバックコールは、ブロックの問題を引き起こす可能性があります。

結論

イベントループは、その究極の複雑さのシンプルさを具体化します。非同期性、糸の安全性、並行性などの困難な問題を解決します。役に立たない部品または不要な部品を削除し、最も効率的な方法でスループットを最大化します。したがって、node.jsプログラマーは、非同期エラーを追いかける時間を短縮し、新しい機能の提供により多くの時間を費やすことができます。

Node.jsイベントループに関するFAQ

node.jsイベントループとは何ですか? node.jsイベントループは、node.jsが非ブロッキング非同期操作を実行できるコアメカニズムです。シングルスレッドイベント駆動型環境でのI/O操作、タイマー、コールバックの処理を担当します。

ノードイベントループはどのように機能しますか?イベントループは、イベントキューのイベントまたはコールバックを継続的にチェックし、追加の順に実行します。ループで実行され、イベントの可用性に基づいてイベントを処理します。これにより、node.jsの非同期プログラミングが可能になります。

node.jsアプリケーションでのイベントループの役割は何ですか?イベントループはnode.jsの中心にあります。これにより、アプリケーションは応答性が高いことを保証し、複数のスレッドなしで多くの同時接続を処理できます。

node.jsイベントループの段階は何ですか? node.jsのイベントループには、タイマー、保留中のコールバック、アイドル、ポーリング、チェック、クロージングなど、いくつかの段階があります。これらのフェーズは、イベントの処理方法と順序を決定します。

イベントループで処理される最も一般的なイベントタイプは何ですか?一般的なイベントには、I/O操作(たとえば、ファイルからの読み取り、ネットワークリクエストの発行)、タイマー(SetimeoutおよびSetIntervalなど)、およびコールバック関数(例えば、非同期操作からのコールバック)が含まれます。

ノードイベントループで長期にわたる操作を処理する方法は?長期にわたるCPU集約型操作は、イベントループをブロックする可能性があり、child_processやworker_threadsモジュールなどのモジュールを使用して、子プロセスまたはワーカースレッドにオフロードする必要があります。

コールスタックとイベントループの違いは何ですか?コールスタックは、現在の実行コンテキストで関数呼び出しを追跡するデータ構造であり、イベントループは非同期操作と非ブロッキング操作の管理を担当します。イベントループがコールバックとI/O操作の実行をスケジュールし、それらをコールスタックにプッシュするため、彼らは協力します。

イベントループの「ティック」とは何ですか? 「ティック」とは、イベントループの単一の反復を指します。各ティックで、イベントループは保留中のイベントをチェックし、実行する準備ができたコールバックを実行します。ティックは、node.jsアプリケーションでの基本的な作業単位です。

以上がnode.jsイベントループ:概念とコードの開発者のガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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