ホームページ >ウェブフロントエンド >jsチュートリアル >Node.js_node.js のタイマーについての簡単な説明

Node.js_node.js のタイマーについての簡単な説明

WBOY
WBOYオリジナル
2016-05-16 15:54:041351ブラウズ

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 イベントが発生すると、対応するコールバックが実行されます。コールバックの実行中に別のタイマーが期限切れになる可能性があるため、タイマーを再度チェックしてコールバックを実行する必要があります。

(実際には、ここでの (4.) は、単なるワンステップ操作ではなく、より複雑です。この説明は、他の詳細を含まず、タイマーの実装に焦点を当てるだけです。)

ノードはループが存在しなくなるまで uv_run を呼び出し続けます。

ノード内の 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 によってグローバル オブジェクトにバインドされます。

以上がこの記事の全内容です。皆さんに気に入っていただければ幸いです。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。