はじめに
JavaScript はほとんどの場合、Node.js およびブラウザーの単一スレッドで実行されます (ワーカー スレッドなどの一部の例外はありますが、これは今回の記事の範囲外です)。この記事では、Node.js における同時実行の仕組みであるイベントループについて解説していきます。
この記事を読み始める前に、スタックとその仕組みについて理解しておく必要があります。このアイデアについては過去に書いたので、「スタックとヒープ — 理解せずにコーディングを始めないでください — Moshe Binieli | スタックとヒープ」を参照してください。中
紹介画像
例
私は例から学ぶのが最善であると信じているので、4 つの簡単なコード例から始めます。例を分析してから、Node.js のアーキテクチャについて詳しく見ていきます。
例 1:
console.log(1);
console.log(2);
console.log(3);
// 出力:
// 1
// 2
// 3
この例は非常に簡単です。最初のステップで console.log(1) がコール スタックに入り、実行されてから削除されます。2 番目のステップで、console.log(2) がコール スタックに入り、実行されます。 console.log(3) については削除されます。
例 1 のコールスタックの視覚化
例 2:
console.log(1);
setTimeout(function foo(){
console.log(2);
}, 0);
console.log(3);
// 出力:
// 1
// 3
// 2
この例では、setTimeout をすぐに実行していることがわかります。そのため、console.log(2) が console.log(3) の前にあると予想されますが、そうではありません。その背後にあるメカニズムを理解しましょう。
基本的なイベント ループ アーキテクチャ (後で詳しく説明します)
スタックとヒープ: これに関する私の記事をチェックしてください (この記事の冒頭にリンクを追加しました)
Web API: Web ブラウザーに組み込まれており、ブラウザーや周囲のコンピューター環境からデータを公開し、それを使って便利で複雑な処理を行うことができます。これらは JavaScript 言語自体の一部ではなく、コア JavaScript 言語の上に構築され、JavaScript コードで使用できる追加のスーパーパワーを提供します。たとえば、Geolocation API は、位置データを取得するための単純な JavaScript 構造をいくつか提供しているため、Google マップ上に自分の位置をプロットすることができます。バックグラウンドでは、ブラウザは実際には複雑な下位コード (C++ など) を使用してデバイスの GPS ハードウェア (または位置データを決定するために利用できるもの) と通信し、位置データを取得し、それをブラウザ環境に返して使用します。あなたのコードに。ただし、この複雑さは API によって抽象化されます。
イベント ループとコールバック キュー: Web API の実行を終了した関数はコールバック キューに移動されます。これは通常のキュー データ構造であり、イベント ループは次の関数をコールバック キューからデキューし、その関数を次のキューに送信する役割を果たします。関数を実行するためのコールスタック。
実行順序
現在呼び出しスタック内にあるすべての関数が実行され、その後呼び出しスタックからポップされます。
呼び出しスタックが空の場合、キューに入れられたすべてのタスクが 1 つずつ呼び出しスタックにポップされて実行され、その後呼び出しスタックからポップされます。
例 2 を理解しましょう
console.log(1) メソッドが呼び出され、コールスタックに配置され、実行されます。
setTimeout メソッドが呼び出され、コール スタックに配置され、実行されます。この実行により、終了時に (すぐに、より正確に言えば、) setTimeout Web API への新しい呼び出しが 0 ミリ秒間作成されます。 「できるだけ早く」と言ったほうがよいでしょう)Web API は呼び出しをコールバック キューに移動します。
console.log(3) メソッドが呼び出され、コールスタックに配置され、実行されます。
イベント ループはコール スタックが空であることを確認し、コールバック キューから「foo」メソッドを取り出してコール スタックに配置し、console.log(2) が実行されます。
例 2 のプロセスの視覚化
したがって、setTimeout(function,lay) の遅延パラメータは、関数が実行されるまでの正確な遅延時間を表すものではありません。これは、ある時点で関数が実行されるまでの最小待機時間を表します。
例 3:
console.log(1);
setTimeout(function foo() {
console.log(‘foo’);
}, 3500);
setTimeout(function boo() {
console.log(‘ブー’);
}, 1000);
console.log(2);
// 出力:
// 1
// 2
// 'ブー'
// 'フー'
例 3 のプロセスの視覚化
例 4:
console.log(1);
setTimeout(function foo() {
console.log(‘foo’);
}, 6500);
setTimeout(function boo() {
console.log(‘ブー’);
}, 2500);
setTimeout(function baz() {
console.log(‘baz’);
}, 0);
for (['A', 'B'] の定数値) {
console.log(値);
}
function two() {
console.log(2);
}
two();
// 出力:
// 1
// 'A'
// 'B'
// 2
// 'バズ'
// 'ブー'
// 'フー'
例 4 のプロセスの視覚化
イベント ループは、タスク キューで待機しているすべてのコールバックを実行します。タスクキュー内では、タスクは大きく 2 つのカテゴリ、すなわちマイクロタスクとマクロタスクに分類されます。
マクロタスク (タスクキュー) とマイクロタスク
より正確に言うと、実際には 2 種類のキューがあります。
マクロタスクキューとマイクロタスクキューに入るタスクは他にもいくつかありますが、一般的なタスクについて説明します。
一般的なマクロタスクは、setTimeout、setInterval、setImmediate です。
一般的なマイクロタスクは、process.nextTick と Promise コールバックです。
実行順序
現在呼び出しスタック内にあるすべての関数が実行され、呼び出しスタックからポップされます。
呼び出しスタックが空の場合、キューに入れられたすべてのマイクロタスクが 1 つずつ呼び出しスタックにポップされて実行され、その後呼び出しスタックからポップされます。
コールスタックとマイクロタスクキューの両方が空の場合、キューに入れられたすべてのマクロタスクは 1 つずつコールスタックにポップされて実行され、その後コールスタックからポップされます。
例 5:
console.log(1);
setTimeout(function foo() {
console.log(‘foo’);
}, 0);
Promise.resolve()
.then(関数 boo() {
console.log(‘ブー’);
});
console.log(2);
// 出力:
// 1
// 2
// 'ブー'
// 'フー'
console.log(1) メソッドが呼び出され、呼び出しスタックに配置され、実行されます。
SetTimeout が実行され、console.log(‘foo’) が SetTimeout Web API に移動され、0 ミリ秒後にマクロタスク キューに移動されます。
Promise.resolve() が呼び出され、解決されてから、.then() メソッドがマイクロタスク キューに移動されます。
console.log(2) メソッドが呼び出され、呼び出しスタックに配置され、実行されます。
イベント ループは呼び出しスタックが空であることを確認し、最初に Promise タスクであるタスクを Micro-Task キューから取得し、console.log(‘boo’) を呼び出しスタックに配置して実行します。
イベント ループはコール スタックが空であることを確認し、次にマイクロ タスクが空であることを確認し、次にマクロ タスク キューから次のタスクである SetTimeout タスクを取得し、console.log('foo') を書き込みます。呼び出しスタックに追加して実行します。
例 5 のプロセスの視覚化
イベントループについての高度な理解
イベント ループ メカニズムの仕組みの低レベルについて書こうと考えていました。それ自体で投稿になる可能性があるため、トピックの紹介と、トピックを詳しく説明する適切なリンクを添付することにしました。
イベントループの下位レベルの説明
Node.js が起動すると、イベント ループが初期化され、非同期 API 呼び出し、タイマーのスケジュール、または process.nextTick() の呼び出しを行う提供された入力スクリプトが処理され (または REPL にドロップされ)、イベント ループの処理が開始されます。
次の図は、イベント ループの操作順序の簡略化された概要を示しています。 (各ボックスはイベント ループの「フェーズ」と呼ばれます。サイクルをよく理解するには、紹介画像を確認してください。)
イベント ループの操作順序の簡略化された概要
各フェーズには、実行するコールバックの FIFO キューがあります (実装によっては別のデータ構造がある可能性があるため、ここでは慎重に述べています)。各フェーズは独自の方法で特殊ですが、一般に、イベント ループが特定のフェーズに入ると、そのフェーズに固有の操作が実行され、キューが使い果たされるかコールバックの最大数に達するまで、そのフェーズのキューでコールバックが実行されます。が実行されました。キューが使い果たされるか、コールバック制限に達すると、イベント ループは次のフェーズに移行します。
フェーズの概要
タイマー: このフェーズでは、setTimeout() および setInterval() によってスケジュールされたコールバックを実行します。
保留中のコールバック: I/O コールバックを次のループ反復に延期して実行します。
アイドル、準備: 内部でのみ使用されます。
ポーリング: 新しい I/O イベントを取得します。 I/O 関連のコールバックを実行します (クローズ コールバック、タイマーによってスケジュールされたコールバック、および setImmediate() を除くほぼすべて)。ノードは、必要に応じてここでブロックします。
確認: setImmediate() コールバックがここで呼び出されます。
クローズ コールバック: いくつかのクローズ コールバック。 socket.on('close', ...).
前の手順はここにどのように当てはまりますか?
したがって、「コールバック キュー」のみを使用した前の手順と、その後の「マクロ キューとマイクロ キュー」を使用した手順は、イベント ループがどのように機能するかについての抽象的な説明でした。
もう 1 つ重要な点があります。イベント ループは、マクロタスク キューから 1 つのマクロタスクを処理した後、マイクロタスク キュー全体を処理する必要があります。
ステップ 1: イベント ループは、ループ時間を現在の実行の現在時刻に更新します。
ステップ 2: マイクロキューが実行されます。
ステップ 3: タイマーフェーズのタスクが実行されます。
ステップ 4: Micro-Queue に何かがあるかどうかを確認し、何かがある場合は Micro-Queue 全体を実行します。
ステップ 5: タイマーフェーズが空になるまでステップ 3 に戻ります。
ステップ 6: 保留中のコールバックフェーズのタスクが実行されます。
ステップ 7: マイクロキューに何かがあるかどうかを確認し、何かがある場合はマイクロキュー全体を実行します。
ステップ 8: 保留中のコールバックフェーズが空になるまで、ステップ 6 に戻ります。
そして、アイドル…マイクロキュー…ポーリング…マイクロキュー…チェック…マイクロキュー…コールバックを閉じると、最初から始まります。
そこで、イベント ループが実際にバックグラウンドでどのように動作するかについて概要を説明しました。実際のドキュメントが説明に優れているため、ここでは触れなかった部分がたくさんあります。素晴らしいリンクを提供します。ドキュメントについては、10 ~ 20 分かけて理解することをお勧めします。
以上がイベントループの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。