この記事では、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++) { }
上記のコードを実行すると、最終的な出力時間が約100ミリ秒ではなく、数秒であることがわかります。これは、スケジュールされたコールバック関数が実際にはループが完了する前に実行されず、ループの終わりまで延期されることを示しています。実際、JavaScript コードの実行中は、すべてのイベントを処理することはできず、現在のコードが完了するまで新しいイベントを処理することはできません。これが、時間のかかる JavaScript コードをブラウザで実行するとブラウザが応答しなくなる理由です。この状況に対処するには、Yielding Processes テクニックを使用して、時間のかかるコードを小さなチャンク (チャンク) に分割し、各チャンクが処理された後に setTimeout を 1 回実行し、短期間の後に次のチャンクを処理することに同意します。この期間中、アイドル時間中、ブラウザ/ノードはキューに入れられたイベントを処理できます。
補足情報
高度なタイマーと生成プロセスについては、第 22 章 JavaScript 高度なプログラミングの高度なテクニック、第 3 版で詳しく説明されています。
Nodeでのタイマー実装
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 が割り当てられることがわかります。関数が呼び出され、最新のカウントが更新されます。 time は、loop.time に割り当てられます。
初期化が完了すると、default_loop_struct.time には初期値が設定され、時間関連の操作はこの値と比較され、対応するコールバック関数を呼び出すかどうかが決定されます。
libuv のイベント スケジューリング コア
前述したように、uv_run 関数は libuv ライブラリのイベント ループ実装のコア部分です。以下はそのフローチャートです:
上記のロジックの簡単な説明は次のとおりです。タイマーに関連するもの:
現在のループの概念で「現在」をマークする現在のループの時間フィールドを更新します。
ループが生きているかどうかを確認します。つまり、タスク (ハンドラー/) があるかどうかを確認します。ループ内で処理する必要があるリクエスト)、そうでない場合はループする必要はありません。
タイマーに指定された時間が現在よりも遅れている場合は、タイマーが期限切れになったことを意味します。コールバック関数が実行されます。
I/O ポーリングを実行します (つまり、スレッドをブロックし、I/O イベントが発生するのを待ちます)。次のタイマーが期限切れになったときに I/O が完了していない場合は、待機を停止して次のタイマー コールバックを実行します。 。
I/O イベントが発生すると、対応するコールバックが実行されます。コールバックの実行中に別のタイマーが期限切れになる可能性があるため、タイマーを再度チェックしてコールバックを実行する必要があります。
(実際には、ここでの (4.) は、単なるワンステップ操作ではなく、より複雑です。この説明は、他の詳細を含まず、タイマーの実装に焦点を当てているだけです。)
ノードは、ループが終了するまで uv_run を呼び出し続けます。生きている。
Nodeのtimer_wrapとタイマー
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 によってグローバル オブジェクトにバインドされます。
以上がこの記事の全内容です。皆さんに気に入っていただければ幸いです。
Node.js のタイマーに関連するその他の記事については、PHP 中国語 Web サイトに注目してください。