ホームページ >ウェブフロントエンド >jsチュートリアル >Node.js_node.js のタイマーについての簡単な説明
Node.js でのタイマーの実装
前のブログ投稿で述べたように、Node のタイマーは新しいスレッドを開いて実装されるのではなく、イベント ループに直接実装されます。以下では、いくつかの JavaScript タイマーの例と Node 関連のソース コードを使用して、タイマー関数が Node でどのように実装されるかを分析します。
JavaScriptのタイマー機能の特徴
Node であってもブラウザであっても、setTimeout と setInterval という 2 つのタイマー関数があり、その動作特性は基本的に同じであるため、以下では分析の例として Node のみを使用します。
JavaScript のタイマーは、コンピューターの基本的なスケジュールされた割り込みと変わらないことがわかっています。割り込みが到着すると、現在実行中のコードが中断され、スケジュールされた割り込み処理関数に転送されます。 JavaScript タイマーが期限切れになると、現在の実行スレッドに実行中のコードがない場合、対応するコールバック関数が実行されます。現在実行中のコードがある場合、JavaScript エンジンはコールバックを実行するために現在のコードを中断しません。 start 新しいスレッドはコールバックを実行しますが、現在のコードが実行された後に処理されます。
console.time('A') setTimeout(function () { console.timeEnd('A'); }, 100); var i = 0; for (; i < 100000; i++) { }
上記のコードを実行すると、最終的な出力時間が約 100ms ではなく、数秒であることがわかります。これは、スケジュールされたコールバック関数が実際にはループが完了する前に実行されず、ループの終わりまで延期されることを示しています。実際、JavaScript コードの実行中はすべてのイベントを処理することはできず、現在のコードが完了するまで新しいイベントを処理する必要があります。これが、時間のかかる JavaScript コードをブラウザで実行するとブラウザが応答しなくなる理由です。この状況に対処するには、Yielding Processes テクニックを使用して、時間のかかるコードを小さなチャンク (チャンク) に分割し、各チャンクが処理された後に setTimeout を 1 回実行し、短期間の経過後に次のチャンクを処理することに同意します。この期間中、アイドル時間中、ブラウザ/ノードはキューに入れられたイベントを処理できます。
補足情報
高度なタイマーと生成プロセスについては、第 22 章「JavaScript 高度なプログラミングの高度なテクニック」第 3 版で詳しく説明されています。
ノードでのタイマー実装
libuv による uv_loop_t 型の初期化
前のブログ投稿では、Node が libuv の uv_run 関数を呼び出して、イベント スケジューリングのためにdefault_loop_ptr を開始すると述べました。default_loop_ptr は、uv_loop_t 型の変数default_loop_struct を指します。ノードが起動すると、uv_loop_init(&default_loop_struct) を呼び出して初期化します。 uv_loop_init 関数の抜粋は次のとおりです。
int uv_loop_init(uv_loop_t* loop) { ... loop->time = 0; uv_update_time(loop); ... }
最初にループの time フィールドに値 0 が割り当てられ、次に uv_update_time 関数が呼び出され、最新のカウント時間がloop.time に割り当てられることがわかります。
初期化が完了すると、default_loop_struct.time に初期値が設定され、時間関連の操作がこの値と比較され、対応するコールバック関数を呼び出すかどうかが決定されます。
libuv のイベント スケジューリング コア
前述したように、uv_run 関数はイベント ループを実装する libuv ライブラリの中核部分です。以下はそのフローチャートです。
タイマーに関連する上記のロジックを簡単に説明します。
現在のループの概念に基づいて「現在」をマークする現在のループの時間フィールドを更新します。
ループが生きているかどうかを確認します。つまり、ループ内に処理する必要のあるタスク (ハンドラー/リクエスト) があるかどうかを確認します。そうでない場合は、ループする必要はありません。
登録されたタイマーを確認します。タイマーに指定された時間が現在よりも遅れている場合は、タイマーが期限切れになったことを意味し、対応するコールバック関数が実行されます。
I/O ポーリングを実行します (つまり、スレッドをブロックし、I/O イベントが発生するのを待ちます)。次のタイマーが期限切れになったときに I/O が完了していない場合は、待機を停止し、次のタイマーのコールバックを実行します。
I/O イベントが発生すると、対応するコールバックが実行されます。コールバックの実行中に別のタイマーが期限切れになる可能性があるため、タイマーを再度チェックしてコールバックを実行する必要があります。
ノードはループが存在しなくなるまで uv_run を呼び出し続けます。
Node には TimerWrap クラスがあり、Node 内で timer_wrap モジュールとして登録されています。
NODE_MODULE_CONTEXT_AWARE_BUILTIN(timer_wrap、node::TimerWrap::Initialize)
TimerWrap クラスは基本的に uv_timer_t を直接カプセル化したもので、NODE_MODULE_CONTEXT_AWARE_BUILTIN は組み込みモジュールを登録するために Node によって使用されるマクロです。
このステップの後、JavaScript はこのモジュールを取得して操作できるようになります。 src/lib/timers.js ファイルは、JavaScript を使用して timer_wrap 関数をカプセル化し、exports.setTimeout、exports.setInterval、exports.setImmediate およびその他の関数をエクスポートします。
前の記事では、Node が開始時に実行環境 LoadEnvironment(env) をロードすると述べました。この関数の非常に重要なステップは、src/node.js をロードし、それを実行することです。src/node.js は指定されたものをロードします。モジュールとグローバルとプロセスを初期化します。もちろん、setTimeout などの関数も src/node.js によってグローバル オブジェクトにバインドされます。
以上がこの記事の全内容です。皆さんに気に入っていただければ幸いです。