Node.jsのコールバックの連鎖の中でメモリ解放を行う方法

投稿者: Anonymous

createHoge, createFuga, execPiyo の三つメソッドがあります。

  • createHoge, createFuga はとても大きなデータを生成する関数で、データを生成したらコールバックを呼び出すものです
  • execPiyo は、createHoge, createFuga で生成したデータを処理する関数で、データ処理が完了したらコールバックを呼び出すものです。
    createHoge(function(hoge) {
        createFuga(function(fuga) {
            execPiyo(hoge, fuga, function() {
                console.log("1piyo done.");
                // この時点で hoge, fuga を破棄したい
                createHoge(function(hoge) {
                    createFuga(function(fuga) {
                        execPiyo(hoge, fuga, function() {
                            console.log("2piyo done.");
                        });
                    });
                });
            });
        });
    });

このようなコールバック呼び出しの連鎖で実行できるものとします。で、連続して execPiyo を何回も実行したい時にcreateHoge, createFugaで生成したhoge, fuga をメモリから解放できずに困っています。

RxJSやPromiseやgeneratorを使ってcreateHoge, createFuga, execPiyoの実行を複数回書いてみたんですが、どのやり方でもメモリを食いすぎて途中で落ちてしまいます。

補足: createHoge, createFuga, execPiyo の一連の処理は一回実行するだけなら問題ないんですが、複数回実行したいけど callback のチェインを続けると途中でメモリが足りなくなります。

何か解決方法はありますか?

解決

createHogeの実装によっては、hogenullを代入しても参照を切れるとは限りません。以下のコードではhogenullを代入していますが、ページごと落ちます。

_x000D_

_x000D_

function createHoge(callback) {_x000D_
  // createHogeを呼ぶたびに10MB確保する_x000D_
  var buffer = new ArrayBuffer(10000000); _x000D_
  callback(buffer);_x000D_
}_x000D_
_x000D_
function createFuga(callback) {_x000D_
  var buffer = new ArrayBuffer(10000000);_x000D_
  callback(buffer);_x000D_
}_x000D_
_x000D_
function execPiyo(a, b, callback) {_x000D_
  callback();_x000D_
}_x000D_
_x000D_
_x000D_
function foo(i, n) {_x000D_
  createHoge(function(hoge) {_x000D_
    createFuga(function(fuga) {_x000D_
      execPiyo(hoge, fuga, function() {_x000D_
        console.log(i + "piyo done.");_x000D_
        // この時点で hoge, fuga を破棄したい_x000D_
_x000D_
        // 破棄しようとしてnull を代入しても無駄_x000D_
        hoge = null;_x000D_
        fuga = null;_x000D_
_x000D_
        if (n < i) return;_x000D_
        foo(i + 1, n);_x000D_
      });_x000D_
    });_x000D_
  });_x000D_
}_x000D_
_x000D_
// 1000回再帰する_x000D_
foo(0, 1000);

_x000D_

_x000D_

_x000D_

以下のようにcreateHogeでローカル変数に参照を持たないようにすると回避できそうですが、実験するとどうもうまく参照を切れていないようです。理由はよくわかりませんが、以下のコードも落ちます。

_x000D_

_x000D_

function createHoge(callback) {_x000D_
  // ローカル変数からバッファを参照していない_x000D_
  callback(new ArrayBuffer(10000000));_x000D_
}_x000D_
_x000D_
function createFuga(callback) {_x000D_
  callback(new ArrayBuffer(10000000));_x000D_
}_x000D_
_x000D_
function execPiyo(a, b, callback) {_x000D_
  callback();_x000D_
}_x000D_
_x000D_
_x000D_
function foo(i, n) {_x000D_
  createHoge(function(hoge) {_x000D_
    createFuga(function(fuga) {_x000D_
      execPiyo(hoge, fuga, function() {_x000D_
        console.log(i + "piyo done.");_x000D_
        // この時点で hoge, fuga を破棄したい_x000D_
_x000D_
        hoge = null;_x000D_
        fuga = null;_x000D_
_x000D_
        if (n < i) return;_x000D_
        foo(i + 1, n);_x000D_
      });_x000D_
    });_x000D_
  });_x000D_
}_x000D_
_x000D_
_x000D_
foo(0, 1000);

_x000D_

_x000D_

_x000D_

createHogeの修正が難しい場合もあるでしょうし、createHogeを呼び出している側からできるひとつの方法としては、以下のようにsetTimeoutなどの非同期な処理を挟むという方法があります。こうすると実際にはコールスタックが積まれていかないので、一見再帰呼び出しのように見えますが正常に完了できます。

_x000D_

_x000D_

function createHoge(callback) {_x000D_
  var buffer = new ArrayBuffer(10000000);_x000D_
  callback(buffer);_x000D_
}_x000D_
_x000D_
function createFuga(callback) {_x000D_
  var buffer = new ArrayBuffer(10000000);_x000D_
  callback(buffer);_x000D_
}_x000D_
_x000D_
function execPiyo(a, b, callback) {_x000D_
  callback();_x000D_
}_x000D_
_x000D_
_x000D_
function foo(i, n) {_x000D_
  createHoge(function(hoge) {_x000D_
    createFuga(function(fuga) {_x000D_
      execPiyo(hoge, fuga, function() {_x000D_
        console.log(i + "piyo done.");_x000D_
        // この時点で hoge, fuga を破棄したい_x000D_
_x000D_
        if (n < i) return;_x000D_
        setTimeout(function() {_x000D_
          foo(i + 1, n);_x000D_
        }, 0)_x000D_
      });_x000D_
    });_x000D_
  });_x000D_
}_x000D_
_x000D_
_x000D_
foo(0, 1000);

_x000D_

_x000D_

_x000D_

なお、この方法では一度の呼び出しに最低でも数ミリ秒のウェイトがかかることになりますが、
もっと短い時間で非同期処理を繰り返す特殊なテクニックもあります。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *