導入
JavaScript はほとんどの場合、Node.js およびブラウザーの単一スレッドで実行されます (ワーカー スレッドなどの一部の例外はありますが、これは今回の記事の範囲外です)。今回はNode.jsにおける同時実行の仕組みであるイベントループについて解説していきます。
この記事を読み始める前に、スタックとその仕組みについて理解しておく必要があります。このアイデアについては過去に書いたので、「スタックとヒープ — 理解せずにコーディングを始めないでください — Moshe Binieli | スタックとヒープ」を参照してください。中くらい
紹介画像
例
私は例から学ぶのが最善であると信じているので、4 つの簡単なコード例から始めます。例を分析してから、Node.js のアーキテクチャについて詳しく見ていきます。
例 1:
コンソール.ログ(1);
コンソール.ログ(2);
コンソール.ログ(3);
// 出力:
// 1
// 2
// 3
この例は非常に簡単です。最初のステップで console.log(1) がコール スタックに入り、実行されてから削除されます。2 番目のステップで、console.log(2) がコール スタックに入り、実行されます。 console.log(3).
例 1 のコールスタックの視覚化
例 2:
コンソール.ログ(1);
setTimeout(function foo(){
コンソール.ログ(2);
}, 0);
コンソール.ログ(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:
コンソール.ログ(1);
setTimeout(function foo() {
console.log(‘foo’);
}, 3500);
setTimeout(function boo() {
console.log(‘ブー’);
}, 1000);
コンソール.ログ(2);
// 出力:
// 1
// 2
// 'ブー'
// 'ふー'
例 3 のプロセスの視覚化
例 4:
コンソール.ログ(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(値);
}
関数 two() {
コンソール.ログ(2);
}
二();
// 出力:
// 1
// 'A'
// 'B'
// 2
// 'バズ'
// 'ブー'
// 'ふー'
例 4 のプロセスの視覚化
イベント ループは、タスク キューで待機しているすべてのコールバックを実行します。タスク キュー内では、タスクは大きく 2 つのカテゴリ、つまりマイクロタスクとマクロタスクに分類されます。
マクロタスク (タスクキュー) とマイクロタスク
より正確に言うと、実際には 2 種類のキューがあります。
マクロタスク キューとマイクロタスク キューに入るタスクは他にもいくつかありますが、一般的なものについては説明します。
一般的なマクロタスクは、setTimeout、setInterval、setImmediate です。
一般的なマイクロタスクは、process.nextTick と Promise callback.
です。
実行順序
現在呼び出しスタックにあるすべての関数が実行され、呼び出しスタックからポップされます。
呼び出しスタックが空の場合、キューに入れられたすべてのマイクロタスクが 1 つずつ呼び出しスタックにポップされて実行され、その後呼び出しスタックからポップされます。
呼び出しスタックとマイクロタスク キューの両方が空の場合、キューに入れられたすべてのマクロタスクは 1 つずつ呼び出しスタックにポップされて実行され、その後呼び出しスタックからポップされます。
例 5:
コンソール.ログ(1);
setTimeout(function foo() {
console.log(‘foo’);
}, 0);
Promise.resolve()
.then(関数 boo() {
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() コールバックがここで呼び出されます。
クローズ コールバック: いくつかのクローズ コールバック。ソケット.on('閉じる', ...).
前の手順はここにどのように当てはまりますか?
したがって、「コールバック キュー」のみを使用した前の手順と、その後の「マクロ キューとマイクロ キュー」を使用した手順は、イベント ループがどのように機能するかについての抽象的な説明でした。
ステップ 1: イベント ループは、ループ時間を現在の実行の現在時刻に更新します。
ステップ 2: マイクロキューが実行されます。
ステップ 3: タイマー フェーズのタスクが実行されます。
ステップ 4: マイクロキューに何かがあるかどうかを確認し、何かがある場合はマイクロキュー全体を実行します。
ステップ 5: タイマーフェーズが空になるまでステップ 3 に戻ります。
ステップ 6: 保留中のコールバックフェーズのタスクが実行されます。
ステップ 7: マイクロキューに何かがあるかどうかを確認し、何かがある場合はマイクロキュー全体を実行します。
ステップ 8: [保留中のコールバック] フェーズが空になるまで、ステップ 6 に戻ります。
そして、アイドル…マイクロキュー…ポーリング…マイクロキュー…チェック…マイクロキュー…コールバックを閉じると、最初から始まります。
そこで、イベント ループが実際にバックグラウンドでどのように動作するかについて概要を説明しました。実際のドキュメントが説明に優れているため、ここでは触れなかった部分がたくさんあります。素晴らしいリンクを提供します。ドキュメントについては、10 ~ 20 分かけて理解することをお勧めします。
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3