web-dev-qa-db-ja.com

node.jsでの並列実行の調整

Node.jsのイベント駆動型プログラミングモデルでは、プログラムフローを調整するのが多少難しくなります。

単純な順次実行はネストされたコールバックに変換されますが、これは十分に簡単です(書き留めるのは少し複雑です)。

しかし、並列実行はどうでしょうか?並行して実行できる3つのタスクA、B、Cがあり、それらが完了したらタスクDに結果を送信するとします。

フォーク/結合モデルでは、これは

  • フォークA
  • フォークB
  • フォークC
  • a、B、Cに参加し、Dを実行する

Node.jsでどのように書くのですか?ベストプラクティスや料理の本はありますか? ソリューションを手動でロールする 毎回する必要がありますか、またはこれを支援するライブラリがありますか?

79
Thilo

Node.jsはシングルスレッドであるため、真に並列的なものはありません。ただし、複数のイベントをスケジュールして、事前に決定できない順序で実行することができます。また、データベースアクセスのようなものは、データベースクエリ自体が個別のスレッドで実行されますが、完了時にイベントストリームに再統合されるという点で、実際には「並列」です。

それでは、複数のイベントハンドラーでコールバックをどのようにスケジュールしますか?さて、これはブラウザ側JavaScriptのアニメーションで使用される一般的なテクニックの1つです。完了を追跡するために変数を使用します。

これはハックのように聞こえますが、それは厄介であり、追跡の実行とそれよりも少ない言語でのグローバル変数の束を残す可能性があります。しかし、javascriptではクロージャーを使用できます。

_function fork (async_calls, shared_callback) {
  var counter = async_calls.length;
  var callback = function () {
    counter --;
    if (counter == 0) {
      shared_callback()
    }
  }

  for (var i=0;i<async_calls.length;i++) {
    async_calls[i](callback);
  }
}

// usage:
fork([A,B,C],D);
_

上記の例では、非同期関数とコールバック関数が引数を必要としないことを想定して、コードをシンプルにしています。もちろん、非同期関数に引数を渡すようにコードを変更し、コールバック関数に結果を蓄積させ、それをshared_callback関数に渡すことができます。


追加の回答:

実際、そのままでも、fork()関数は既にクロージャーを使用して非同期関数に引数を渡すことができます:

_fork([
  function(callback){ A(1,2,callback) },
  function(callback){ B(1,callback) },
  function(callback){ C(1,2,callback) }
],D);
_

あとは、A、B、Cからの結果を蓄積してDに渡すだけです。


さらに追加の答え:

抵抗できませんでした。朝食中にこれについて考え続けた。以下は、結果を蓄積するfork()の実装です(通常、コールバック関数に引数として渡されます)。

_function fork (async_calls, shared_callback) {
  var counter = async_calls.length;
  var all_results = [];
  function makeCallback (index) {
    return function () {
      counter --;
      var results = [];
      // we use the arguments object here because some callbacks 
      // in Node pass in multiple arguments as result.
      for (var i=0;i<arguments.length;i++) {
        results.Push(arguments[i]);
      }
      all_results[index] = results;
      if (counter == 0) {
        shared_callback(all_results);
      }
    }
  }

  for (var i=0;i<async_calls.length;i++) {
    async_calls[i](makeCallback(i));
  }
}
_

それは十分簡単でした。これにより、fork()がかなり汎用的になり、複数の不均一なイベントを同期するために使用できます。

Node.jsでの使用例:

_// Read 3 files in parallel and process them together:

function A (c){ fs.readFile('file1',c) };
function B (c){ fs.readFile('file2',c) };
function C (c){ fs.readFile('file3',c) };
function D (result) {
  file1data = result[0][1];
  file2data = result[1][1];
  file3data = result[2][1];

  // process the files together here
}

fork([A,B,C],D);
_

更新

このコードは、async.jsなどのライブラリまたはさまざまなpromiseベースのライブラリが存在する前に作成されました。私はasync.jsがこれに触発されたと信じたいのですが、その証拠はありません。とにかく..今日これを行うことを考えているなら、async.jsまたは約束を見てください。上記の答えを、async.parallelのようなものがどのように機能するかについての良い説明/図として考えてください。

完全を期すために、以下に_async.parallel_を使用した方法を示します。

_var async = require('async');

async.parallel([A,B,C],D);
_

_async.parallel_は、上で実装したfork関数とまったく同じように機能することに注意してください。主な違いは、node.jsの規則に従って、Dの最初の引数としてエラーを渡し、2番目の引数としてコールバックを渡すことです。

約束を使用して、次のように記述します。

_// Assuming A, B & C return a promise instead of accepting a callback

Promise.all([A,B,C]).then(D);
_
126
slebetman

現在、「非同期」モジュールはこの並列機能を提供し、上記のfork関数とほぼ同じであると考えています。

10
Wes Gamble

futures モジュールには join というサブモジュールがあり、これを使用するのが好きです:

pthread_joinがスレッドに対して機能する方法と同様に、非同期呼び出しを結合します。

Readmeは、フリースタイルの使用またはPromiseパターンを使用した future サブモジュールの使用の良い例を示しています。ドキュメントの例:

var Join = require('join')
  , join = Join()
  , callbackA = join.add()
  , callbackB = join.add()
  , callbackC = join.add();

function abcComplete(aArgs, bArgs, cArgs) {
  console.log(aArgs[1] + bArgs[1] + cArgs[1]);
}

setTimeout(function () {
  callbackA(null, 'Hello');
}, 300);

setTimeout(function () {
  callbackB(null, 'World');
}, 500);

setTimeout(function () {
  callbackC(null, '!');
}, 400);

// this must be called after all 
join.when(abcComplete);
5
Randy

別のオプションは、ノードのステップモジュールです。 https://github.com/creationix/step

2
Wilhelm Murdoch

ここでは簡単な解決策が可能かもしれません: http://howtonode.org/control-flow-part-ii パラレルアクションまでスクロールします。別の方法は、A、B、およびCがすべて同じコールバック関数を共有し、その関数にグローバルまたは少なくとも機能外のインクリメンターを持たせることです。3つすべてがコールバックを呼び出してからDを実行します。もちろん、A、B、Cの結果もどこかに保存する必要があります。

2
Alex

一般的な約束と非同期ライブラリに加えて、3番目のエレガントな方法があります-「配線」を使用します:

var l = new Wire();

funcA(l.branch('post'));
funcB(l.branch('comments'));
funcC(l.branch('links'));

l.success(function(results) {
   // result will be object with results:
   // { post: ..., comments: ..., links: ...}
});

https://github.com/garmoshka-mo/mo-wire

0