ノードイベントループにおけるイベント実行の順序

ノードイベントループにおけるイベント実行の順序

イベントループ

ブラウザ環境では、js には独自のイベント ループがあり、node 環境にも同様のイベント ループがあります。

ブラウザ環境イベントループ

まず、ブラウザのイベント ループを確認しましょう。

要約すれば:

まず、メインスレッドの同期コードが実行されます。同期コードの各行は実行スタックにプッシュされ、非同期コードの各行は非同期 API (タイマースレッド、Ajax スレッドなど) にプッシュされます。実行スタックに実行するコードがない場合、つまり現在のメインスレッドに同期コードがない場合、タスクキューは非同期タスクのマイクロタスクキューからマイクロタスクを取得し、タスクキューに入れて実行し、そのコールバック関数再び実行スタックに入れて実行しますマイクロタスクキューが空の場合、非同期タスクはマクロタスクから取得されてタスクキューに追加され実行スタックにプッシュされてコールバック関数を実行し、マクロタスク内の同期タスクと非同期タスクの検索を続けます。1 サイクルでイベント ループ (イベント ポーリング) が完了します。

ブラウザ環境での例:

例:

        コンソールログ("1");
        タイムアウトを設定する(() => {
            コンソールにログ出力します。
        }, 1);
        新しいPromise((res, rej) => {
            console.log("約束");
            res('PromiseRes')
        }).then(val => {
            コンソールログ(val);
        })
        コンソールログ("2");

分析:
まず、同期コードの最初の行を見つけて、実行のためにそれを直接スローしますISERES)は、この時点で同期コードが再び遭遇し、現在のメインスレッドの同期コードが実行されています。実行されたパリマスのプロミスのために、[マクロタスクのキューからマクロタスクタイマーを使用する必要があります。

ノード環境イベントループ

ノードでは、イベント ループは主に6 つのステージに分かれています。

外部データ入力 –> ポーリングフェーズ –> チェックフェーズ –> シャットダウンイベントコールバックフェーズ –> タイマーフェーズ –> I/Oコールバックフェーズ –> アイドルフェーズ –> ポーリングフェーズ… ループ開始

6つのステージ

写真はインターネットから

ここに画像の説明を挿入

  • タイマー フェーズ: タイマーのコールバック (setTimeout、setInterval) を実行するために使用されます。
  • I/Oコールバックフェーズ: 前のサイクルで実行されなかったいくつかのI/Oコールバックを処理します。
  • アイドル、準備フェーズ: ノードによって内部的にのみ使用されるため、必要ありません。
  • ポーリング フェーズ: 新しい I/O 時間を取得します。適切な条件下では、ノードはここでブロックされます。
  • チェックフェーズ: setImmediate() のコールバックを実行します。
  • コールバックのクローズフェーズ: ソケットのクローズタイムコールバックを実行する

主なステージ
タイマー:
タイマー フェーズは、setTimeout および setInterval コールバックを実行し、ポーリング フェーズによって制御されます。
同様に、ノード内のタイマーによって指定された時間は正確な時間ではなく、できるだけ早く実行されることしかできません。
投票:
投票フェーズでは、システムは次の 2 つの処理を実行します。
1. タイマーステージに戻り、コールバックを実行します。
2. I/Oコールバックを実行し、この段階に入るときにタイマーが設定されていない場合は、次の2つのことが起こります。

ポーリングキューが空でない場合は、コールバックキューを走査し、キューが空になるかシステム制限に達するまで同期的に実行します。ポーリングキューが空の場合は、次の2つのことが起こります。
1. 実行する必要があるsetImmediateコールバックがある場合、ポーリングフェーズは停止し、チェックフェーズに入り、コールバックを実行します。
2. 実行すべき setImmediate コールバックがない場合、コールバックがキューに追加されるまで待機し、コールバックをすぐに実行します。待機を防ぐためにタイムアウト設定もあります。もちろん、タイマーが設定されていてポーリング キューが空の場合は、タイマーがタイムアウトしたかどうかを判断します。タイムアウトした場合は、タイマー ステージに戻ってコールバックを実行します。

チェックステージ
setImmediate() のコールバックがチェック キューに追加されます。イベント ループのステージ図から、チェック ステージの実行順序はポーリング ステージの後であることがわかります。チェック ステージに入ると、ポーリングは何かあるかどうかを確認し、その後チェック ステージに進みます。ない場合は、直接タイマー ステージに進みます。

(1)setTimeoutとsetImmediate

これら 2 つは非常に似ていますが、主な違いは呼び出しのタイミングです。

setImmediate は、ポーリング フェーズ、つまりチェック フェーズが完了したときに実行されるように設計されており、チェック フェーズでのみ実行されます。
setTimeout は、ポーリング フェーズがアイドル状態で、設定された時間に達したときに実行されるように設計されていますが、タイマー フェーズで実行されます。つまり、現在のスレッドには実行可能な他の同期タスクがなく、タイマーはタイマー フェーズで実行されます。

これら 2 つの実行のタイミングは、次の前または後のいずれかになります。
例1:

// // 非同期タスク内のマクロタスク setTimeout(() => {
    console.log('===setTimeout===');
},0);
setImmediate(() => {
    console.log('===setImmediate===')
})

ここに画像の説明を挿入

繰り返し実行した結果は異なり、ランダム感があ​​ります。その理由は主にsetTimeoutの実装コードに関係しています。timeパラメータを渡さなかったり、0に設定したりした場合、nodejsは1、つまり1msの値を取ります(ブラウザ側の値はもっと大きい場合があり、ブラウザによっても異なります)。したがって、コンピュータのCPUが1ms以内にタイマーフェーズを実行できるほど強力である場合、時間遅延が要件を満たさないためコールバックは実行されず、2回目の実行まで待つことしかできず、setIntervalが最初に実行されます。
上記のランダムな現象は、CPU が同じタスクを複数回実行するのに要する時間のわずかな差によって発生し、1 ミリ秒以内で変動する可能性があります。
通常、setTimeoutが0の場合、setImmediateの前に実行されます。

例2:
渡した値がタイマー実行のコールバック時間よりも大きい場合、次のイベントループでタイマーが直接実行されます。

タイムアウトを設定する(() => {
    console.log('===setTimeout===');
},10);
setImmediate(() => {
    console.log('===setImmediate===')
})

ここに画像の説明を挿入

例3:
上記のコードを i/o に配置すると、常に最初にチェックが行われ、次にタイマーがチェックされます。

定数 fs = require('fs');

fs.readFile("./any.js", (データ) => {
    タイムアウトを設定する(() => {
        console.log('===setTimeout===');
    },10);
    setImmediate(() => {
        console.log('===setImmediate===')
    })
});

ここに画像の説明を挿入

ループの最初のラウンドでは、ファイルが読み取られます。コールバックでは、チェックフェーズに入り、setImmediate を実行し、タイマーフェーズでタイマーが実行されます。
setimmediate と settimeout が同じ I/O ループ内で呼び出される場合は、setImmediate が常に最初に呼び出されます。

(2)プロセス.nextTick

この関数は実際にはイベント ループから独立しています。独自のキューがあります。各ステージが完了すると、nextTick キューがある場合は、キュー内のすべてのコールバック関数がクリアされ、他のマイクロタスクの前に最初に実行されます。

例1:

タイムアウトを設定する(() => {
 コンソールログ('timer1')
 Promise.resolve().then(function() {
   コンソールログ('promise1')
 })
}, 0)
プロセス.nextTick(() => {
 コンソールログ('nextTick')
 プロセス.nextTick(() => {
   コンソールログ('nextTick')
   プロセス.nextTick(() => {
     コンソールログ('nextTick')
     プロセス.nextTick(() => {
       コンソールログ('nextTick')
     })
   })
 })
})
// nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1

例2:

定数 fs = require('fs');

fs.readFile("./any.js", (データ) => {
    process.nextTick(()=>console.log('process===2'))
    タイムアウトを設定する(() => {
        console.log('===setTimeout===');
    },10);
    setImmediate(() => {
        console.log('===setImmediate===')
    })
});
process.nextTick(()=>console.log('process===1'))

ここに画像の説明を挿入

練習例

非同期関数 async1() {
    コンソールログ('2')
    //awaitが終了するまで待機しますが、次のマイクロタスクawait async2()に入るため、それ以上実行されません。
    コンソールログ('9')
  }
   
   関数async2() {
    コンソールログ('3')
  }
   
  コンソールログ('1')
   
  setTimeout(関数() {
    コンソールログ('11')
  }, 0)
   
  setTimeout(関数() {
    コンソールログ('13')
  }, 300)
   
  setImmediate(() => console.log('12'));
   
  process.nextTick(() => console.log('7'));
   
  非同期1();
   
  process.nextTick(() => console.log('8'));
   
  新しいPromise(関数(resolve) {
    コンソールログ('4')
    解決する();
    コンソールログ('5')
  }).then(関数() {
    コンソールログ('10')
  })
   
  コンソールログ('6')

分析:
上記の順序はシリアル番号の順序です。
初版1号:
前に 2 つの関数宣言があり、いずれも同期コードのこの行 1 を直接出力します。
印刷2:
1 を出力した後、すべてのコードは非同期です。非同期タスク キューに追加され、async1 関数に直接呼び出されます。この関数では 2 が出力されます。
プリント3:
async1 関数は async await 関数なので、async2 関数の実行を待機する偽装された同期操作でもあります。async2 が実行された後、await は promise の then 操作を受け入れるため、9 は直接印刷されません。そのため、promise に属するコールバック操作はマイクロタスクであり、マイクロタスク キューに追加されます。
プリント4:
process.nextTick はマイクロタスクなので、promise の実行を継続し、4 を出力します。
プリント5:
resolve() のコールバックはすぐには実行されず、マイクロタスクに属します。マイクロタスク キューに追加されるため、5 が出力されます。
プリント6:
最後のメインスレッド同期コードは 6 を出力します。
印刷7と8:
process.nextTick は他のタイマーよりも優先度が高いため、コールバック関数が直接実行され、7 と 8 が出力されます。
プリント9、10:
このとき、マイクロタスク キュー内のマイクロタスクを実行する必要があります。現在、9 と 10 の 2 つがあります。順番に、最初に 9 が印刷され、次に 10 が印刷されます。
プリント11、12:
setTimeout は 0 秒で、setImmediate よりも早いです。実行順序では、最初に 11 が印刷され、後で 12 が印刷されます。
プリント13:
setTimeout が 300ms の関数は 13 を出力します。

例:

非同期関数 async1() {
    コンソールログ('2')
    //awaitが終了するまで待機しますが、次のマイクロタスクawait async2()に入るため、それ以上実行されません。
    コンソールログ('9')
  }
   
   関数async2() {
    コンソールログ('3')
  }
   
  コンソールログ('1')
   
  setTimeout(関数() {
    コンソールログ('11')
    タイムアウトを設定する(() => {
        コンソールログ('11-1');
    },100);
    setImmediate(() => {
        コンソールログ('11-2')
    })
  }, 0)
   
  setTimeout(関数() {
    コンソールログ('13')
    タイムアウトを設定する(() => {
        コンソールログ('15');
    },10);
    setImmediate(() => {
        コンソールログ('14')
    })
  }, 300)
  setImmediate(() => console.log('12'));
  process.nextTick(() => console.log('7'));
  非同期1();
   
  process.nextTick(() => console.log('8'));
   
  新しいPromise(関数(resolve) {
    コンソールログ('4')
    解決する();
    コンソールログ('5')
  }).then(関数() {
    コンソールログ('10')
  })
   
  コンソールログ('6')

要約:

ノードイベントループにおけるイベント実行順序に関する記事はこれで終了です。ノードイベント実行順序の詳細については、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

参考: https://www.cnblogs.com/everlose/p/12846375.html

以下もご興味があるかもしれません:
  • Node.js ストリームの ondata トリガーのタイミングと順序の調査

<<:  MySQL トランザクション分離レベルの表示と変更の例

>>:  Docker で Harbor パブリック リポジトリを構築する方法の例

推薦する

MySQLデータ損失の原因と解決策

目次序文問題の説明原因分析拡大する総括する序文最近、データの欠落やデータの損失に関するフィードバック...

ノードにおけるhttpモジュールの使用と実行プロセス

ノードにおけるhttpの役割は何ですか? httpモジュールの役割は、サーバーの作成と記述を支援する...

Docker で TLS と CA 認証を有効にする方法

目次1. 証明書を生成する2. リモートを有効にする3. リモート接続3.1 Jenkins接続3....

ボリュームを使用してホストと Docker コンテナ間でファイルを転送する方法

以前、Docker コンテナとローカル マシン間のファイル転送に関する記事を書きました。しかし、この...

VMwareを使用したPermeateレンジシステムのインストール手順の詳細説明

1. 背景私たちは時々社内研修を行っており、実験環境をよく利用しています。最初はdockerコンテナ...

Spring Boot Docker パッケージング ツールの概要

目次スプリングブートDocker spring-boot-maven-プラグインSpotify Ma...

HTML にオーディオファイルを挿入してブラウザで再生する場合の互換性の問題

HTML にオーディオ ファイルを挿入した後 (mp3 ファイルを再生した後) に発生したいくつかの...

Vueの自己ネストツリーコンポーネントの使い方の詳細な説明

この記事では、Vueの自己ネストツリーコンポーネントの使い方を参考までに紹介します。具体的な内容は次...

Ubuntu 16.4 で完全に分散された Hadoop 環境を構築するための実践的なチュートリアル

序文この記事は主にubantu 16.4 Hadoop完全分散構築に関する関連コンテンツを紹介し、皆...

Mysql は非集計列を選択できません

1. はじめに最近ブログをアップグレードし、記事ページの下部に前の記事と次の記事に直接ジャンプできる...

MySQLデータベースの基本構文と操作

MySQLデータベースの基本構文DDL操作データベース作成構文: create database デ...

MySQL インデックス データ構造の詳細な分析

目次概要インデックスデータ構造バイナリツリー赤黒木BツリーB+ツリーハッシュ索引InnoDB インデ...

Dockerボリュームコンテナ間のデータ共有の実装

ボリュームとは何ですか?ボリュームは英語で容量を意味し、Docker ではデータ ボリューム、つまり...

MySQL SQL文を最適化するためのヒント

十分に最適化されていない、またはパフォーマンスが極端に低い SQL ステートメントに直面した場合、通...

MySQLセグメンテーション関数substring()の具体的な使用法

MySQL には、主に left()、right()、substring()、substring_i...