ディレクトリ
- マイクロタスク
- イベントループメカニズム
- SetImmediate、setTimeout / setIntervalおよびprocess.nextTick実行時間の比較
- ケーススタディ
- 参考資料
1.マイクロタスク
Nodeのイベントループメカニズムについて説明する前に、Nodeの「マイクロタスク」の説明を追加しましょう。ここで説明するマイクロタスクは、実際には一般的な用語であり、2つの部分で構成されています。
- process.nextTick()登録済みコールバック(nextTickタスクキュー)
- promise.then()登録済みコールバック(promiseタスクキュー)
Nodeがマイクロタスクを実行すると、nextTickタスクキュー内のタスクが優先され、実行後はPromiseタスクキュー内のタスクが引き続き実行されます。したがって、process.nextTickのコールバックとpromise.thenのコールバックがメインスレッドまたはイベントループの同じステージにある場合、process.nextTickのコールバックはpromise.thenのコールバックよりも優先される必要があります。
2.イベントループメカニズム
図に示すように、ノードによって実行されるプロセス全体を表します。非ブロッキング非同期コードが実行されると(タイマーの作成、ファイルの読み取りと書き込みなど)、イベントループに入ります。イベントサイクルは6つの段階に分かれています。
Pendingコールバック、Idle / Prepare、CloseコールバックステージはNodeによって内部的に使用される3つのステージであるため、ここでは主に開発者コードの実行に直接関連するTimers、Poll、Checkの3つのステージを分析します。
タイマー(タイマーフェーズ):図からわかるように、最初にイベントループに入ると、タイマーフェーズから開始します。この段階で、期限切れのタイマーコールバック(setTimeoutおよびsetIntervalを含む)があるかどうかを判断します。存在する場合は、期限切れのタイマーコールバックがすべて実行されます。実行後、対応するマイクロタスクがコールバックでトリガーされると、すべてのマイクロタスクが実行されます、そして、マイクロタスクの実行後、保留中のコールバックステージに入ります。
保留中のコールバック:次のループ反復に延期されるI / Oコールバック(システム関連のコールバック)。
アイドル/準備:内部使用のみ。(詳細)
投票(投票段階):
コールバックキューが空でない場合:
コールバックが実行されます。対応するマイクロタスクがコールバックでトリガーされた場合、ここでのマイクロタスクの実行タイミングは他の場所と異なり、すべてのコールバックが実行されるまで待機しません。対応するマイクロタスク。全復帰実行後は以下の状態になります。
コールバックキューが空の場合(コールバックがないか、すべてのコールバックが実行されます):
ただし、実行されないタイマー(setTimeout、setInterval、およびsetImmediate)がある場合、ポーリングフェーズは終了し、チェックフェーズに入ります。それ以外の場合は、進行中のI / O操作が完了するのをブロックして待機し、すべてのコールバックが完了するまで、対応するコールバックをすぐに実行します。
チェック(クエリフェーズ):setImmediateに関連するコールバックがあるかどうかをチェックします。存在する場合は、すべてのコールバックが実行されます。実行後、対応するマイクロタスクがコールバックでトリガーされると、すべてのマイクロタスクが実行されます。コールバックステージを閉じます。
コールバックが閉じる使用:閉じるは、次のようないくつかのコールバック、実行することsocket.on('close', ...)
などを。
概要とメモ:
- 各ステージにはFIFOコールバックキューがあり、次のステージに入る前に、現在のステージのすべてのコールバックを完了するか、システム関連の制限に到達しようとします。
- ポーリングフェーズで実行されるマイクロタスクのタイミングは、タイマーとチェックフェーズのタイミングとは異なります。前者は各コールバックの実行後に対応するマイクロタスクを実行し、後者はすべてのコールバックの実行後に対応するマイクロタスクを実行します。 。
3. SetImmediate、setTimeout / setInterval、およびprocess.nextTick実行タイミングの比較
setImmediate:非同期コールバックをトリガーします。これは、イベントループのチェックフェーズ中に直ちに実行されます。
setTimeout:非同期コールバックをトリガーします。タイマーが期限切れになると、イベントループのタイマーフェーズで実行され、1回だけ実行されます(clearTimeoutでキャンセルできます)。
setInterval:非同期コールバックをトリガーし、タイマーが期限切れになるたびに、イベントループのタイマーフェーズでコールバックが実行されます(clearIntervalでキャンセルできます)。
process.nextTick:メインスレッド(メインライン)で実行できるマイクロタスク(非同期)コールバックをトリガーし、イベントシーケンスの特定のフェーズで実行できます。
4.ケース分析
最初のグループ:
setTimeoutとsetImmediateを比較します。
// test.js
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
結果:
分析:
出力結果からは、「setTimeout」が最初か「setImmediate」が最初のどちらかで、出力は不確実です。イベントループプロセスの分析から、イベントループが開始され、タイマーステージに入ります。setTimeoutで設定された遅延は0ですが、ノードのsetTimeoutの遅延値の範囲は[1、2 ^ 31 -1]この範囲では、それ以外の場合はデフォルトで1になります。したがって、プロセスのパフォーマンスの制約により、タイマーがタイマーステージに達したときにタイマーが期限切れになっていない可能性があるため、次のプロセスに進むため、「setImmediate」出力が前面に表示されることがあります状況。setTimeoutの遅延が10など適切に増加している場合、基本的には「setImmediate」が最初に出力されます。
2番目のグループ:
メインスレッド(メインライン)、タイマーステージ、ポーリングステージ、チェックステージの実行順序と、対応するマイクロタスクの実行順序を比較します。
// test.js
const fs = require('fs');
console.log('mainline: start')
process.nextTick(() => {
console.log('mainline: ', 'process.nextTick\n')
})
let counter = 0;
const interval = setInterval(() => {
console.log('timers: setInterval.start ', counter)
if(counter < 2) {
setTimeout(() => {
console.log('timers: setInterval.setTimeout')
process.nextTick(() => {
console.log('timers microtasks: ', 'setInterval.setTimeout.process.nextTick\n')
})
}, 0)
fs.readdir('./', (err, files) => {
console.log('poll: setInterval.readdir1')
process.nextTick(() => {
console.log('poll microtasks: ', 'setInterval.readdir1.process.nextTick')
process.nextTick(() => {
console.log('poll microtasks: ', 'setInterval.readdir1.process.nextTick.process.nextTick')
})
})
})
fs.readdir('./', (err, files) => {
console.log('poll: setInterval.readdir2')
process.nextTick(() => {
console.log('poll microtasks: ', 'setInterval.readdir2.process.nextTick')
process.nextTick(() => {
console.log('poll microtasks: ', 'setInterval.readdir2.process.nextTick.process.nextTick\n')
})
})
})
setImmediate(() => {
console.log('check: setInterval.setImmediate1')
process.nextTick(() => {
console.log('check microtasks: ', 'setInterval.setImmediate1.process.nextTick')
})
})
setImmediate(() => {
console.log('check: setInterval.setImmediate2')
process.nextTick(() => {
console.log('check microtasks: ', 'setInterval.setImmediate2.process.nextTick\n')
})
})
} else {
console.log('timers: setInterval.clearInterval')
clearInterval(interval)
}
console.log('timers: setInterval.end ', counter)
counter++;
}, 0);
console.log('mainline: end')
結果:
分析:
メインラインに示されているように、メインスレッドのprocess.nextTickは、同期コードが実行された後、イベントループの前に実行されることがわかります。
最初のタイマーに示されているように、この時点で、イベントループは初めてTimersステージに到達し、setIntervalの遅延時間が上がっているため、コールバックが実行されます。対応する直接対応するマイクロタスクがないため、次のステージに直接入ります。
最初のポーリングに示すように、この時点で、イベントループは初めてポーリングステージに到達します。タイマーステージで以前に実行されたコールバックから、2つの非ブロッキングI / O操作(readdir)がトリガーされます。このステージでは、I / O操作が完了すると、対応する2つのコールバックが直接実行されます。各コールバックが実行された後、対応するマイクロタスクが実行されることが出力からわかります。マイクロタスクでトリガーされたマイクロタスクは実行を継続し、すべてのコールバックが実行されるまで待機せず、マイクロタスクをトリガーします。これは期待どおりです。すべてのコールバックが実行された後、タイマーがまだスケジュールされているため、ポーリングフェーズが終了し、チェックフェーズに入ります。
この時点での最初のチェックに示すように、この時点で、イベントループは初めてCheckステージに到達し、対応する2つのsetImmediateの実行を直接トリガーします。出力から、すべてのコールバックが実行された後にマイクロタスクがトリガーされていることがわかります。これは期待どおりです。マイクロタスクを実行した後、後の段階に入ります。
このときの2番目のタイマーに示されているように、この時点で、イベントループは2回目のTimersステージに到達し、「timers:setInterval.setTimeout」が最初に出力されます。setIntervalコールバックが初めて実行されたとき、その内部のsetTimeout(...、0)は実際には1回実行されましたが、マイクロタスクをトリガーできなかったため、そのコールバックは実行されずに入力されたことを忘れないでください後のステージで、再びタイマーステージに到達するまで待機します。FIFOに従って、前のsetTimeoutコールバックが最初に実行され、次にsetIntervalコールバックが実行されます。最後に、すべてのコールバックが完了した後、setTimeoutコールバックでトリガーされたマイクロタスクが実行されます。最後の出力は「タイマーマイクロタスク:setInterval.setTimeout.process.nextTick」です。これは期待どおりです(すべてのコールバックが実行された後、対応するマイクロタスクが実行されます)。
次の出力は類似しているため、これ以上の分析は行われません。
5.リファレンス