Node.jsの非同期プログラミング:イベントループの詳細な理解
javascriptはシングルスレッドですが、これにより、node.jsが最新のアーキテクチャを活用する能力が制限されますか?最大の課題の1つは、マルチスレッドの固有の複雑さに対処することです。新しいスレッドを作成し、スレッド間のコンテキストの切り替えを管理するのは高価です。オペレーティングシステムとプログラマーの両方が、多数のエッジケースを処理するソリューションを提供するために多くの努力が必要です。この記事では、node.jsがイベントループを介してこの問題を解決する方法を説明し、node.jsイベントループのさまざまな側面を調査し、それがどのように機能するかを示します。イベントループは、Node.jsのキラー機能の1つです。これは、まったく新しい方法でこのトリッキーな問題を解決するためです。
ループ自体は半無効です。つまり、コールスタックまたはコールバックキューが空である場合、ループを終了できます。コールスタックは、Console.logなどの同期コードと見なすことができます。 node.jsは、下にあるLibuvを使用して、着信接続からのコールバックのオペレーティングシステムを投票します。
イベントループが単一のスレッドで実行される理由は疑問に思うかもしれません。スレッドは、各接続に必要なデータのメモリが比較的重いです。スレッドは、開始する必要があるオペレーティングシステムリソースであり、数千のアクティブな接続に拡張することはできません。
通常、マルチスレッドは状況を複雑にする可能性があります。コールバックがデータを返す場合、実行中のスレッドにコンテキストを元に戻す必要があります。コールスタックやローカル変数など、現在の状態を同期する必要があるため、スレッド間のコンテキストの切り替えは遅くなります。イベントループは、単一のスレッドであるため、複数のスレッドがリソースを共有する場合、バグを回避できます。シングルスレッドループは、スレッドセーフエッジケースを減らし、より高速なコンテキストスイッチングを可能にします。これは、ループの背後にある本当の天才です。スケーラビリティを維持しながら、接続とスレッドを効果的に利用します。
理論では、コードがどのように見えるかを見てみましょう。好きなようにREPLで実行するか、ソースコードをダウンロードできます。
イベントループが答えなければならない最大の質問は、ループがアクティブであるかどうかです。その場合は、コールバックキューを待つ時間を決定します。各反復で、ループはコールスタックとポーリングを拡張します。
これはメインループをブロックする例です:
setTimeout( () => console.log('Hi from the callback queue'), 5000); // 保持循环活动这么长时间 const stopTime = Date.now() + 2000; while (Date.now() < stopTime) {}
コールバックキュー
const stopTime = Date.now() + 2000; while (Date.now() < stopTime) {} // 这需要 7 秒才能执行 setTimeout(() => console.log('Ran callback A'), 5000);
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に深く連れて行きたいです
これらは、イベントループフェーズです:
画像出典:libuv document
以下は、このタイムアウト計算の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...');
スレッドプール
これは全体的な構造です:
ネットワーク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():
が必要ないくつかの理由
setTimeout( () => console.log('Hi from the callback queue'), 5000); // 保持循环活动这么长时间 const stopTime = Date.now() + 2000; while (Date.now() < stopTime) {}
結論
Node.jsイベントループに関するFAQ
node.jsイベントループとは何ですか? node.jsイベントループは、node.jsが非ブロッキング非同期操作を実行できるコアメカニズムです。シングルスレッドイベント駆動型環境でのI/O操作、タイマー、コールバックの処理を担当します。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 サイトの他の関連記事を参照してください。