node.jsのタイマーアーキテクチャ

node.jsでタイマーとして使用されている、このようなすばらしい広く使用されているツールと、setTimeout、setInterval関数、およびnetモジュールでの使用についてお話したいと思います。 node.jsでは、 timers.jsカーネルモジュールがタイマーを担当します。 setTimeoutは、このモジュールからグローバルにアクセス可能な関数です。



ソースコードで簡単にコメントを見つけることができます。

多くのソケットが同じタイムアウトを持っているという事実のため、それぞれのタイマー( libuvからの低レベルタイマーを意味する)を使用しません。 オーバーヘッドが大きすぎます。 代わりに、同じタイムアウトモーメントを持つソケットのパケットにこのようなタイマーを1つ使用します。 ソケットを二重接続リストに統合します 。 この手法は、libuvのドキュメントで説明されています: http ://pod.tst.eu/http: //cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts




このドキュメントでは、4つの異なる手法について説明しています。 解決された問題:ソケットがアクティブになるたびに、たとえば新しいデータが到着するたびに、タイマーを延長する必要があります。

テクニックは、複雑さと有効性の増加にリストされています。

1.アクティビティごとにタイマーを停止、再初期化、開始します。 ほとんど常に悪い。 プラスはありません。

2. ev_timer_againでタイマーを延長します。 通常は正常に機能する非常にシンプルなソリューション。

3.最初に計画されたときにタイマーを動作させ、その後、別のタイマーでどれだけ延長する必要があるかを確認します。 より困難ですが、場合によってはより効率的に機能します。

4.タイムアウトに二重リンクリストを使用します。 タイマーを延長する必要がある場合は、二重にリンクされたリストの最後に単純に転送されます。 さらに難しくなりますが、非常に効果的です。

4番目のオプションはnode.jsに実装されています。



setTimeout(コールバック、ミリ秒、[引数]、[...])



次のコードを実行するとどうなりますか?

var start = Date.now(); //    ,     . var Timeout100 = setTimeout(function() {}, 100); //     10 var Timeout110 = setTimeout(function() {}, 100); var Timeout210 = setTimeout(function() {}, 200);
      
      





そして、次のことが起こります。 timers.jsモジュールには、listと呼ばれるモジュール変数があり、(ほぼ)すべてのアクティブなタイマーを保存します。

簡単に言うと、setTimeoutの内部ロジックは次のように表すことができます。

 function setTimeout(callback, msecs) { var list = lists[msecs]; //      ,   ,     //     msecs. if (!list) { //    process.binding('timer_wrap').Timer ,  libuv' . list = lists[msecs] = new Timer(); //    ,   . list.ontimeout = ...; //  . list.start(msecs, 0); //  ,       . L.init(list); } //   ,       callback  msecs . var item = Timeout; item._idleStart = Date.now(); //    item._idleTimeout = msecs; //   item._onTimeout = callback; //    //      . // ,         //   item',  ..     ,    //      . L.append(list, item); return item; }
      
      







したがって、タイマー変数には2つのキーが含まれます。





起動時+ 100msのlibuvは、Timer100をノックします、と彼らは言います。 これに対する反応は、そのハンドラーの実行になります。これについては後で説明することを約束しました。



このハンドラーは何をしますか? その論理もそれほど複雑ではありません。

 //      ,     ,    function callback(Timer list, msecs) { var now = Date.now(); var first; //       ,    ,    , //    ,   .   . while(first = L.peek()) { // ,     var wait = item._idleStart + item._idleTimeout - now; //     if (wait <= 0) { //     L.remove(first); //  callback first.onTimeout(); } else { //    wait  list.start(wait, 0); // , ..      -   //       return; } } //     , ,   . //  . list.stop(); //   delete lists[msecs]; }
      
      





この場合、このコールバックは3回呼び出されます。

  1. 開始+ 100瞬間:

    1. Timeout1を実行します
    2. Timeout2の前に10ミリ秒が残っていることがわかります。この時間のタイマーを設定します
    3. 出てきます。
  2. 開始+ 110モーメント:

    1. Timeout2を実行します。
    2. シートが空であることがわかります。Timer100とキーリスト[100]を削除します。
    3. 出てきます。
  3. 開始+ 210モーメント:

    1. Timeout3を実行します。
    2. シートが空であることがわかります。Timer200とキーリスト[200]を削除します。
    3. 出てきます。




clearTimeout(タイムアウト)



次に、clearTimeoutがどのように機能するかを見てみましょう。 この関数は、setTimeoutから返されたTimeoutクラスの同じオブジェクトを引数として受け取ります。

 function clearTimeout(item) { //    . L.remove(item); var msecs = item._idleTimeout; //   var list = lists[msecs]; //   ,  if (list && L.isEmpty(list)) { list.close(); delete lists[msecs]; } }
      
      





したがって、対応するTimerが引き続き機能する場合、リスト内の削除されたアイテムは検出されず、したがって、実行されません。 初期化の直後にclearTimeout(Timeout2)を実行することにより、次のことを行います。









setInterval(コールバック、ミリ秒、[arg]、[...])およびclearInterval(間隔)



setTimeoutとは異なり、setIntervalおよびclearIntervalはトリックを使用しません。 間隔ごとに、新しいlibuv'shタイマーが作成され、ミリ秒ミリ秒ごとに繰り返しモードで充電されます。 clearintervalでは、停止して削除されます。 それだけです



ソケット(ネットモジュール)



ソケットは上記の関数を使用しません。

ソケットは頻繁にタイムアウトが延長されるという特徴があるため、各パケットのタイマーを作成/削除することはありませんが、リスト構造自体に追加されます。 これを行うために、彼らはタイマーモジュールの文書化されていない機能を使用します(しかし、私はあなたにそれを教えませんでした!)。

したがって、実際には、リストの構造は次のようになります。



プロトタイプ内のソケットにはすでに_onTimeoutメソッドがあるため、クロージャーは必要ありません。

これらは、プロパティ_idleStart、_idleTimeout(時間追跡のプロパティ)、_ idleNextおよび_idlePrev(二重リンクリストのプロパティ)によって単純に拡張されます。

データが送受信されると、ソケットは対応する二重リンクリストから単に削除されます...



...そしてすぐにその最後に追加されました:





この記事を書くことの副作用:



  1. プルリクエストが node.js 送信され、setTimeout()が5%、まれに50%スピードアップされました。
  2. node.jsにプルリクエストを送信し、タイマーの誤った開始を修正します。
  3. Ubuntu 12.04(2番目-11.04)を実行しているマシンでは、node.jsおよびブラウザーの32ビット+ PAE Date.now()関数が、本来の15倍遅いことがわかりました。 ポイントはgettimeofday(2)の基になる呼び出しだと思います。 64ビットUbuntu 12.10にアップグレードすると、この問題は解決します。




結論:



  1. 使用するツールのソースコードを読んでください-あなたは多くを学びます。
  2. Node.js開発者は、タイマーを操作するためのベストプラクティスを順守していますが、実装にはいくつかの省略があります。
  3. タイムアウトのキューのレーキは、サイクルで同期的に発生します。
  4. 内部タイマーを生成しないように、同じタイムアウト値を使用すると便利です。



All Articles