ホームページ  >  記事  >  ウェブフロントエンド  >  Nodejs環境におけるJavaScriptの実行メカニズムとイベントループ

Nodejs環境におけるJavaScriptの実行メカニズムとイベントループ

不言
不言転載
2019-04-02 10:59:592491ブラウズ

この記事は、JavaScript の高階関数の使用法を紹介します。一定の参考価値があります。困っている友人は参考にしてください。お役に立てれば幸いです。

1. 説明

nodejs は単一スレッドで実行され、イベント駆動型のノンブロッキング IO プログラミング モデルに基づいています。これにより、非同期操作の結果が返されるのを待たずにコードの実行を続けることができます。非同期イベントがトリガーされると、メインスレッドに通知され、メインスレッドは対応するイベントのコールバックを実行します。

この記事では、ノードでの JavaScript コードの実行処理について説明します。以下はテストコードです。出力結果がわかっている場合は、この記事を読む必要はありません。知らない場合は、この記事を読む必要はありません。結果を出力してから、このビデオをご覧ください:

console.log(1)
setTimeout(function () {
  new Promise(function (resolve) {
    console.log(2)
    resolve()
  })
  .then(() => { console.log(3) })
})
setTimeout(function () {
  console.log(4)
})

複雑な内容:

setTimeout(() => {
  console.log('1')
  new Promise((resolve) => { console.log('2'); resolve(); })
  .then(() => { console.log('3') })
  new Promise((resolve)=> { console.log('4'); resolve()})
  .then(() => { console.log('5') })
  setTimeout(() => { 
    console.log('6')
    setTimeout(() => {
      console.log('7')
      new Promise((resolve) => { console.log('8'); resolve() })
      .then( () => {  console.log('9') })
      new Promise((resolve) => { console.log('10'); resolve() })
      .then(() => {  console.log('11') })
    })
    setTimeout(() => { console.log('12') }, 0)
  })
  setTimeout(() => { console.log('13') }, 0)
})
setTimeout(() => { console.log('14') }, 0)
new Promise((resolve) => { console.log('15'); resolve() })
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })
.then(() => { console.log('18') })

2.nodejs

ノードの起動プロセス.js の起動プロセスは次のステップに分けることができます。

  1. platformInit メソッドを呼び出して、nodejs 実行環境を初期化します。
  2. performance_node_start メソッドを呼び出して、nodejs のパフォーマンス統計を実行します。
  3. opensslの設定の判断。
  4. v8_platform.Initialize を呼び出して、libuv スレッド プールを初期化します。
  5. V8::Initialize を呼び出して、V8 環境を初期化します。
  6. nodejs 実行インスタンスを作成します。
  7. 前の手順で作成したインスタンスを起動します。
  8. js ファイルの実行を開始し、同期コードが実行された後、イベント ループに入ります。
  9. リッスンするイベントがない場合、nodejs インスタンスは破棄され、プログラムの実行が完了します。

Nodejs環境におけるJavaScriptの実行メカニズムとイベントループ

#3.nodejs イベント ループの詳細説明

Nodejs はメッセージ ループを 6 つのステージ (公式の Called Phase) に細分化します。 )、各ステージには、そのステージで処理する必要があるコールバック関数を格納するキューのような構造があります。したがって、メッセージ ループの各反復では、各ステージで実行されるコールバックの最大数があります。この数を超えると、現在のステージは強制的に終了され、次のステージに入ります。このルールは、メッセージ ループ内のすべてのコールバックに適用されます。メッセージ ループ。1 ステージ。

3.1 タイマー ステージ

これはメッセージ ループの最初のステージであり、

for

ループを使用してすべての

setTimeout## を処理します。 # および setInterval callbacks.これらのコールバックは最小ヒープ (最小ヒープ) に保存されます。このようにして、エンジンは毎回ヘッダー要素を判断するだけで済みます。タイマー フェーズは、条件が満たされない場合、またはキューが空の場合にのみ終了します。特定のコールバックが条件を満たしているかどうかを判断するメソッドタイマー フェーズも非常に単純です。メッセージ ループは、タイマーに入るたびに現在のシステム時間を保存します。その後、上記の最小ヒープ内のコールバック関数によって設定された起動時間が、タイマーに入るときに保存された時間を超えているかどうかを確認するだけです。

3.2 保留中の I/O コールバック フェーズ

close callbacks

,

setTimeout( を除くほぼすべてのコールバックを実行します。 )

setInterval()setImmediate() コールバック、たとえばTCP 接続エラーfs.readsocket および IO 操作用のその他のコールバック関数、およびさまざまなエラー用のコールバック。3.3 アイドル、準備フェーズシステム内の一部の呼び出し。

3.4 ポーリング ステージ、重要なステージ

これはメッセージ サイクル全体で最も重要なステージであり、メッセージ サイクル メカニズム全体をサポートしているため、その機能は非同期リクエストとデータを待機することです。

ポーリング フェーズには 2 つの主な機能があります。1 つは下限時間に達したタイマーのコールバックを実行すること、もう 1 つはポーリング キュー内のイベントを処理することです。

注:


Node の多くの API は、fs.readFile などのイベント サブスクリプションに基づいています。これらのコールバックは、poll ステージで完了する必要があります。 イベント ループがポーリング フェーズに入ると:

#poll

キューが空でない場合、イベント ループは最初にキューを横断し、キューがクリアまたは実行されたコールバックの数がシステム制限に達するまで、コールバックを同期的に実行します。
  • poll
  • キューが空の場合、2 つの状況が考えられます。
  • コードが setImmediate()

    によるコールバックで設定されている場合、イベント ループは
      poll
    • ステージを直接終了し、 ステージに入ります。 check ステージ。check キューでコールバックを実行します。 コードが設定されていない場合はsetImmediate()
    • コールバックを設定します:
      • タイマーが設定されている場合、イベント ループはこの時点でタイマーをチェックします。1 つ以上のタイマーが下限に達している場合、イベント ループはタイマー ステージにラップバックして、有効なタスクを実行します。タイマーキューのコールバック。
      • タイマーが設定されていない場合、イベント ループは この時点でブロックされます イベント コールバックがポーリング キューに追加されるまで、ポーリング フェーズで待機しています。

ポーリング フェーズで、JS レイヤー コードによって登録されたイベント コールバックが返されない場合、イベント ループはポーリング フェーズで一時的にブロックされます。条件:

  1. ポーリング フェーズが実行されると、ポーリング フェーズの最大ブロック時間であるタイムアウト タイムアウトが渡されます。
  2. タイムアウト時間が経過していない場合、イベントが返されると、イベントに登録されているコールバック関数が実行されます。タイムアウトの期限が切れると、ポーリング フェーズが終了し、次のフェーズが実行されます。

このタイムアウトの適切な設定は何ですか? 答えは、タイマー フェーズで最近実行されるコールバックの開始時刻と現在との差です。その差が detal であると仮定します。ポーリング フェーズの後に実行を待っているものはありません。コールバック。したがって、ここでの最大待機時間はデルタです。期間中にイベントがメッセージ ループを起動した場合は、次のフェーズの作業を続行します。期間中に何も起こらない場合は、次のフェーズの作業を続行します。 、タイムアウト後も、メッセージ ループは引き続き次のフェーズに入る必要があるため、次の反復のタイマー フェーズも実行できます。
Nodejs は、ポーリング フェーズを通じてメッセージ ループ全体を駆動し、IO イベントと到着を待ちます。

3.5 チェック フェーズ

このステージでは、setImmediate のコールバック関数のみが処理されます。
では、なぜ setImmediate を処理するための特別なステージがあるのでしょうか? 簡単に言えば、これは、ポーリング ステージがいくつかのコールバックを設定し、ポーリング ステージの後に実行されることを期待しているためです。そのため、チェック ステージがポーリング ステージの後に追加されます。

3.6 コールバックを閉じるステージ

いくつかの処理に特化しています。 close タイプのコールバック。たとえば、socket.on('close', .. .)。リソースのクリーニングに使用されます。

4. Nodejs の実行 JS コード プロセスとイベント ループ プロセス

1. ノードの初期化

ノード環境の初期化

入力したコードを実行

Executeprocess.nextTickCallback

マイクロタスクを実行します

2. イベント ループに入る

2.1. Timer ステージに入る

  • Timer## が実行されているかどうかを確認します# キューに期限切れの Timer コールバックがあります。そうであれば、すべての期限切れの Timer コールバックを TimerId
  • の昇順で実行します。 # があるかどうかを確認します。 ##process.nextTick
  • タスクがある場合は、すべて実行します マイクロタスク (約束) があるかどうかを確認し、ある場合はすべて実行します
  • #このステージを終了します
  • 2.2、「
  • Pending I/O Callback
」と入力します。 Phase

Pending I/O Callback
    のコールバックがあるかどうかを確認します。したがって、コールバックを実行します。
  • このステージを終了しない場合process.nextTick
  • タスクがあるかどうかを確認し、存在する場合はすべて実行します
  • タスクがあるかどうかを確認しますマイクロタスク (約束) がある場合は、すべてを実行します
  • このステージを終了します
  • 2.3.
  • idle、prepare
ステージに入ります

このステージにはJavaScript とはほとんど関係がないため、

2.4 をスキップして

Poll

ステージに入ります

まず、未完了のコールバックがあるかどうかを確認します。存在する場合は、2 つの状況が考えられます: ## 最初のケース: 実行可能なコールバックがあります。

利用可能なコールバックをすべて実行します (期限切れのタイマーや一部の IO イベントなどを含む)

プロセスがあるかどうかを確認します。 .nextTick

タスクがある場合は、すべて実行します

##マイクロタスク (約束) があるかどうかを確認し、ある場合はすべて実行します

##このステージを終了します2 番目のタイプ 状況: 実行可能なコールバックがありません


immediate

コールバックがあるかどうかを確認し、存在する場合はポーリング フェーズを終了します。そうでない場合は、このステージでブロックし、新しいイベント通知を待ちます。

未完了のコールバックがない場合は、ポーリング ステージを終了します。

2.5、check# を入力します。 ##フェーズ

即時コールバックがある場合は、すべての即時コールバックを実行します


process.nextTick

タスクがあるかどうかを確認し、存在する場合はすべて実行します

マイクロタスク (約束) があるかどうかを確認し、ある場合はすべて実行します

このステージを終了します

2.6、クロージング
ステージに入ります

if 即時コールバックがある場合は、すべての即時コールバックを実行します。

process.nextTick

タスクがあるかどうかを確認し、存在する場合はすべて実行します。チェックマイクロタスクがあるかどうか (約束)、はいの場合はすべて実行します

このステージを終了します

3. アクティブな ハンドル (タイマー、IO、およびその他のイベント ハンドル) があるかどうかを確認します

ある場合は、イベント ループの次のラウンドを続行します。

ない場合は、イベント ループを終了してプログラムを終了します。

注:


イベント ループの各サブステージは、終了する前に次のプロセスを順番に実行します:

process.nextTick コールバックがあるかどうかを確認します。したがって、それらをすべて実行してください。 マイクロタスク(お約束)があるかどうかを確認し、あればすべて実行します。

4.1 关于Promise和process.nextTick

事件循环队列先保证所有的process.nextTick回调,然后将所有的Promise回调追加在后面,最终在每个阶段结束的时候一次性拿出来执行。

此外,process.nextTickPromise回调的数量是受限制的,也就是说,如果一直往这个队列中加入回调,那么整个事件循环就会被卡住

Nodejs環境におけるJavaScriptの実行メカニズムとイベントループ

4.2 关于setTimeout(…, 0) 和 setImmediate

这两个方法的回调到底谁快?

如下面的例子:

setImmediate(() => console.log(2))
setTimeout(() => console.log(1))

使用nodejs多次执行后,发现输出结果有时是1 2,有时是2 1

对于多次执行输出结果不同,需要了解事件循环的基础问题。

首先,Nodejs启动,初始化环境后加载我们的JS代码(index.js).发生了两件事(此时尚未进入消息循环环节):

setImmediate 向 Check 阶段 中添加了回调 console.log(2);

setTimeout 向 Timer 阶段 中添加了回调 console.log(1)

这时候, 要初始化阶段完毕, 要进入 Nodejs 消息循环了。

为什么会有两种输出呢? 接下来一步很关键:

当执行到 Timer 阶段 时, 会发生两种可能. 因为每一轮迭代刚刚进入 Timer 阶段 时会取系统时间保存起来, 以 ms(毫秒) 为最小单位.

如果 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间, 则执行 Timer 阶段 中的该回调. 这种情况下先输出 1, 直到 Check 阶段 执行后,输出2.总的来说, 结果是 1 2.

如果运行比较快, Timer 阶段 中回调预设的时间可能刚好等于消息循环所保存的时间, 这种情况下, Timer 阶段 中的回调得不到执行, 则继续下一个 阶段. 直到 Check 阶段, 输出 2. 然后等下一轮迭代的 Timer 阶段, 这时的时间一定是满足 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间 , 所以 console.log(1) 得到执行, 输出 1. 总的来说, 结果就是 2 1.

所以, 输出不稳定的原因就取决于进入 Timer 阶段 的时间是否和执行 setTimeout 的时间在 1ms 内. 如果把代码改成如下, 则一定会得到稳定的输出:

require('fs').readFile('my-file-path.txt', () => {
 setImmediate(() => console.log(2))
 setTimeout(() => console.log(1))
});

这是因为消息循环在 Pneding I/O Phase 才向 Timer 和 Check 队列插入回调. 这时按照消息循环的执行顺序, Check 一定在 Timer 之前执行。

从性能角度讲, setTimeout 的处理是在 Timer Phase, 其中 min heap 保存了 timer 的回调, 因此每执行一个回调的同时都会涉及到堆调整. 而 setImmediate 仅仅是清空一个队列. 效率自然会高很多.

再从执行时机上讲. setTimeout(..., 0) 和 setImmediate 完全属于两个阶段.

5. 一个实际例子演示

下面以一段代码来说明nodejs运行JavaScript的机制。

如下面一段代码:

setTimeout(() => {                                                // settimeout1
  console.log('1')
  new Promise((resolve) => { console.log('2'); resolve(); })      // Promise3
  .then(() => { console.log('3') })
  new Promise((resolve)=> { console.log('4'); resolve()})         // Promise4
  .then(() => { console.log('5') })
  setTimeout(() => {                                              // settimeout3
    console.log('6')
    setTimeout(() => {                                            // settimeout5
      console.log('7')
      new Promise((resolve) => { console.log('8'); resolve() })   // Promise5
      .then( () => {  console.log('9') })
      new Promise((resolve) => { console.log('10'); resolve() })  // Promise6
      .then(() => {  console.log('11') })
    })
    setTimeout(() => { console.log('12') }, 0)                    // settimeout6
  })
  setTimeout(() => { console.log('13') }, 0)                      // settimeout4
})
setTimeout(() => { console.log('14') }, 0)                        // settimeout2
new Promise((resolve) => { console.log('15'); resolve() })        // Promise1
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })        // Promise2
.then(() => { console.log('18') })

上面代码执行过程:

node初始化

执行JavaScript代码

遇到setTimeout, 把回调函数放到Timer队列中,记为settimeout1

遇到setTimeout, 把回调函数放到Timer队列中,记为settimeout2

遇到Promise,执行,输出15,把回调函数放到微任务队列,记为Promise1

遇到Promise,执行,输出17,把回调函数放到微任务队列,记为Promise2

代码执行结束,此阶段输出结果:15 17

没有process.nextTick回调,略过

执行微任务

检查微任务队列是否有可执行回调,此时队列有2个回调:Promise1、Promise2

执行Promise1回调,输出16

执行Promise2回调,输出18

此阶段输出结果:16 18

进入第一次事件循环

进入Timer阶段

检查Timer队列是否有可执行的回调,此时队列有2个回调:settimeout1、settimeout2

执行settimeout1回调:

输出1、2、4

添加了2个微任务,记为Promise3、Promise4

添加了2个Timer任务,记为settimeout3、settimeout4

执行settimeout2回调,输出14

Timer队列任务执行完毕

没有process.nextTick回调,略过

检查微任务队列是否有可执行回调,此时队列有2个回调:Promise3、Promise4

按顺序执行2个微任务,输出3、5

此阶段输出结果:1 2 4 14 3 5

Pending I/O Callback阶段没有任务,略过

进入 Poll 阶段

检查是否存在尚未完成的回调,此时有2个回调:settimeout3、settimeout4

settimeout3 コールバックを実行します

出力 6

settimeout5、settimeout6 として記録される 2 つのタイマー タスクを追加しました

settimeout4 コールバックを実行します、出力 13

process.nextTick コールバックはありません、スキップ

マイクロタスクはありません、スキップ

このステージの出力結果: 6 13

チェックフェーズと終了フェーズにはタスクがありません。スキップしてください。

アクティブな ハンドル (タイマー、IO、その他のイベント) がまだあるかどうかを確認してください。ハンドル) 、はい、イベント ループの次のラウンドに進みます

2 番目のイベント ループに入ります

タイマー フェーズに入ります

タイマーが開始されているかどうかを確認しますキューには実行可能なコールバックがあります。このキューには 2 つのコールバックがあります: settimeout5、settimeout6

settimeout5 コールバックを実行します:

出力 7、8、10

2 つのマイクロタスクを追加し、Promise5 として記録されます。Promise6

は settimeout6 コールバックを実行し、12

を出力します。process.nextTick コールバックはありません。 # をスキップします。

##マイクロタスク キューに実行可能なコールバックがあるかどうかを確認します。この時点で、キューには 2 つのコールバックがあります: Promise5、Promise6


2 つのマイクロタスクを順番に実行し、出力 9、11


このステージの出力結果:

7 8 10 12 9 11

保留中の I/O コールバック、ポーリング、チェックにはタスクはありません、および終了フェーズがあるため、スキップしてください


アクティブな

ハンドル (タイマー、IO、その他のイベント ハンドル) がまだあるかどうかを確認し、それ以上ない場合は、イベント ループを終了しますそしてプログラムを終了します

プログラム実行の終了、出力結果:

15 17 16 18 1 2 4 14 3 5 6 13 7 8 10 12 9 11

Nodejs環境におけるJavaScriptの実行メカニズムとイベントループ##[関連する推奨事項:

JavaScript ビデオ チュートリアル

]

以上がNodejs環境におけるJavaScriptの実行メカニズムとイベントループの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。