Node JS がシングルスレッドでありながらマルチスレッドである理由については、「ノードの内部」と呼ばれる記事で説明しました。これにより、Node のアーキテクチャに関する強固な基礎が得られ、イベント ループの魔法を理解するための準備が整います!
ノード js は、イベント ループのためシングルスレッドであると見なすことができます。ところで、イベントループとは何でしょうか?
技術的な詳細を理解しやすくなると思うので、私はいつもレストランの例えから始めます。
レストランでは、メインシェフが注文リストから注文を受け取り、アシスタントのチームに渡します。料理が出来上がるとシェフが料理を提供します。 VIPのお客様がいらっしゃる場合は、シェフがこちらのオーダーを優先させていただきます。
このアナロジーを考慮すると、次のように言えます...
Node JS イベント ループのコンテキスト内。
シェフは、タスクと作業の委任を管理するイベント ループです。
チーム・オブ・アシスタンスは、ワーカー・スレッドまたはワーカー・スレッドに委任されたタスクの実行を処理する OS です。
オーダーリストは順番を待っているタスクのタスクキューです。
VIP 顧客は優先度が高く、通常のタスクよりも前に完了するマイクロタスクです。
イベント ループを理解するには、まずマイクロタスクとマクロタスクの違いを理解する必要があります。
マイクロタスクとは、優先度の高いタスクを意味し、現在実行中の Javascript コードが完了した後、イベント ループの次のフェーズに移る前に実行されます。
例:
これらは、イベント ループの後のフェーズで実行するためにキューに入れられる優先度の低いタスクです。
例:
Node.js で非同期タスクを実行する場合、イベント ループがすべての中心となります。
イベント ループのおかげで、Node.js はノンブロッキング I/O 操作を効率的に実行できます。これは、時間のかかるタスクをオペレーティング システムまたはワーカー スレッドに委任することで実現されます。タスクが完了すると、そのコールバックが体系的に処理され、メインスレッドをブロックすることなくスムーズな実行が保証されます。
これは、Node.js がシングルスレッドでありながら複数のタスクを同時に処理できるようにする魔法です。
イベント ループには 6 つのフェーズがあり、各フェーズには特定の種類のタスクを保持する独自のキューがあります。
1.タイマーフェーズ
このフェーズでは、setTimeout や setInterval などのタイマー関連のコールバックが処理されます。
ノード js は、遅延が期限切れになったコールバックのタイマー キューをチェックします。
タイマー遅延が満たされると、そのコールバックが実行のためにこのキューに追加されます。
console.log('Start'); setTimeout(() => { console.log('Timer 1 executed after 1 second'); }, 1000); setTimeout(() => { console.log('Timer 2 executed after 0.5 seconds'); }, 500); let count = 0; const intervalId = setInterval(() => { console.log('Interval callback executed'); count++; if (count === 3) { clearInterval(intervalId); console.log('Interval cleared'); } }, 1000); console.log('End');
出力:
Start End Timer 2 executed after 0.5 seconds Timer 1 executed after 1 second Interval callback executed Interval callback executed Interval callback executed Interval cleared
2.I/O コールバックフェーズ
このフェーズの目的は、ファイルの読み取りまたは書き込み、データベースのクエリ、ネットワーク リクエストの処理、その他の非同期 I/O タスクなど、完了した I/O (入力/出力) 操作のコールバックを実行することです。
Node.js で非同期 I/O 操作 (fs.readFile を使用したファイルの読み取りなど) が発生すると、その操作はオペレーティング システムまたはワーカー スレッドに委任されます。これらの I/O タスクは、メインスレッドの外部でノンブロッキング方式で実行されます。タスクが完了すると、結果を処理するためにコールバック関数がトリガーされます。
I/O コールバック フェーズでは、操作の終了後にこれらのコールバックが実行のためにキューに入れられます。
const fs = require('fs'); console.log('Start'); fs.readFile('example.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file:', err); return; } console.log('File contents:', data); }); console.log('Middle'); setTimeout(() => { console.log('Simulated network request completed'); }, 0); console.log('End');
出力
Start Middle End Simulated network request completed File contents: (contents of the example.txt file)
3.アイドルフェーズ
このフェーズでは、ユーザー定義の作業は実行されず、代わりにイベント ループが次のフェーズの準備を整えます。この段階では内部調整のみが行われます。
4.ポーリングフェーズ
ポーリング フェーズでは、処理する必要がある保留中の I/O イベント (ネットワーク アクティビティやファイル システム イベントなど) があるかどうかを確認します。これらのイベントに関連付けられたコールバックがすぐに実行されます。
保留中の I/O イベントがない場合、ポーリング フェーズはブロック状態になる可能性があります。
このブロック状態では、Node.js は新しい I/O イベントが到着するのを待つだけになります。このブロック状態は、Node.js を非ブロックにするものです。Node.js は、新しい I/O イベントがコールバック実行をトリガーするまで待機し、その間、メイン スレッドを他のタスクのために解放したままにします。
完了した I/O 操作 (fs.readFile、HTTP リクエスト、データベース クエリなど) のコールバックはすべて、このフェーズ中に実行されます。これらの I/O 操作は、以前のフェーズ (タイマー フェーズや I/O コールバック フェーズなど) で開始され、現在完了している可能性があります。
setTimeout または setInterval でタイマーが設定されている場合、Node.js は期限切れのタイマーがあるかどうか、および関連付けられたコールバックを実行する必要があるかどうかを確認します。タイマーの期限が切れた場合、コールバックはコールバック キューに移動されますが、次のフェーズであるタイマー フェーズまで処理されません。
const fs = require('fs'); const https = require('https'); console.log('Start'); fs.readFile('file1.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file1:', err); return; } console.log('File1 content:', data); }); fs.readFile('file2.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file2:', err); return; } console.log('File2 content:', data); }); https.get('https://jsonplaceholder.typicode.com/todos/1', (response) => { let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { console.log('HTTP Response:', data); }); }); console.log('End');
出力:
Start End File1 content: (contents of file1.txt) File2 content: (contents of file2.txt) HTTP Response: (JSON data from the HTTP request)
5.フェーズの確認
投票フェーズのタスクが完了した後。このフェーズは主に setImmediate コールバックの実行を処理します。このコールバックは、ポーリング フェーズで I/O イベントが処理された直後に実行されるようにスケジュールされています。
setImmediate コールバックは、システムが I/O イベントの処理でビジー状態になってからタスクを実行するなど、現在のイベント ループ サイクルの後にアクションを実行する場合によく使用されます。
チェック フェーズは、タイマー フェーズ (setTimeout と setInterval を処理する) よりも優先されます。これは、タイマーが期限切れになった場合でも、setImmediate コールバックは常にタイマーの前に実行されることを意味します。
setImmediate は、現在の I/O サイクルの後、次のタイマー サイクルの前にコールバックが実行されることを保証します。これは、他のタスクを実行する前に I/O 関連のタスクを確実に完了させたい場合に重要です。
console.log('Start'); setTimeout(() => { console.log('Timer 1 executed after 1 second'); }, 1000); setTimeout(() => { console.log('Timer 2 executed after 0.5 seconds'); }, 500); let count = 0; const intervalId = setInterval(() => { console.log('Interval callback executed'); count++; if (count === 3) { clearInterval(intervalId); console.log('Interval cleared'); } }, 1000); console.log('End');
出力:
Start End Timer 2 executed after 0.5 seconds Timer 1 executed after 1 second Interval callback executed Interval callback executed Interval callback executed Interval cleared
6.クローズフェーズ
コールバックを閉じるフェーズは通常、アプリケーションを終了またはシャットダウンする前にクリーンアップする必要があるときに実行されます。
このフェーズでは、ネットワーク ソケットやファイル ハンドルなどのシステム リソースが不要になったときに実行する必要があるイベントとタスクを処理します。
このフェーズがないと、アプリケーションはファイル ハンドル、ネットワーク接続、またはその他のリソースを開いたままにし、メモリ リーク、データ破損、またはその他の問題を引き起こす可能性があります。
const fs = require('fs'); console.log('Start'); fs.readFile('example.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file:', err); return; } console.log('File contents:', data); }); console.log('Middle'); setTimeout(() => { console.log('Simulated network request completed'); }, 0); console.log('End');
出力:
Start Middle End Simulated network request completed File contents: (contents of the example.txt file)
Node JS のイベント ループには、もう 1 つの特別なフェーズがあります。
マイクロタスクキュー
process.nextTick() は、イベント ループの特別なフェーズでコールバックを実行することを約束します。
process.nextTick() は、現在の操作が完了した直後、イベント ループが次のフェーズに進む前にコールバックが実行されるようにスケジュールします。
process.nextTick() は、イベント ループのどのフェーズにも含まれません。代わりに、現在実行中の同期コードの直後、イベント ループのフェーズに入る前に実行される独自の内部キューがあります。
現在の操作の後、I/O、setTimeout、またはイベント ループでスケジュールされたその他のタスクの前に実行されます。
Promise は process.nextTick() よりも優先度が低く、すべての process.nextTick() コールバックの後に処理されます。
const fs = require('fs'); const https = require('https'); console.log('Start'); fs.readFile('file1.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file1:', err); return; } console.log('File1 content:', data); }); fs.readFile('file2.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file2:', err); return; } console.log('File2 content:', data); }); https.get('https://jsonplaceholder.typicode.com/todos/1', (response) => { let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { console.log('HTTP Response:', data); }); }); console.log('End');
出力:
Start End File1 content: (contents of file1.txt) File2 content: (contents of file2.txt) HTTP Response: (JSON data from the HTTP request)
これで、イベント ループがどのように機能するかについて全体的なアイデアが得られました。
質問が 1 つありますので、コメント欄に回答してください。
const fs = require('fs'); console.log('Start'); fs.readFile('somefile.txt', 'utf8', (err, data) => { if (err) { console.error(err); return; } console.log('File content:', data); }); setImmediate(() => { console.log('Immediate callback executed'); }); setTimeout(() => { console.log('Timeout callback executed'); }, 0); console.log('End');
ありがとうございます。
あなたの答えを待っています。
以上がノード JS - イベント ループの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。