在node.js中协调并行执行


79

node.js的事件驱动编程模型使协调程序流变得有些棘手。

简单的顺序执行变成嵌套的回调,这很容易(尽管有些麻烦以至于要写下来)。

但是并行执行呢?假设您有三个可以并行运行的任务A,B,C,当它们完成后,您要将其结果发送给任务D。

对于前叉/连接模型,这将是

  • 叉子A
  • B叉
  • C叉
  • 加入A,B,C,运行D

我该如何在node.js中编写它?是否有最佳做法或食谱?我是否必须每次都手动解决方案,还是有一些带有帮助程序的库?

Answers:


128

在node.js中,没有什么是真正并行的,因为它是单线程的。但是,可以安排多个事件并按照您无​​法事先确定的顺序运行。而且,诸如数据库访问之类的事情实际上是“并行的”,因为数据库查询本身在单独的线程中运行,但在完成后会重新集成到事件流中。

那么,如何安排多个事件处理程序的回调?好吧,这是在浏览器端javascript中的动画中使用的一种常用技术:使用变量来跟踪完成。

这听起来像是骇客,确实如此,听起来很混乱,可能会留下一堆全局变量来进行跟踪,而使用的语言则更少。但是在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或promises。只要考虑上面的答案,就可以很好地解释/说明async.parallel之类的事情。

为了完整起见,以下是您的处理方式async.parallel

var async = require('async');

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

请注意,其async.parallel工作原理与fork我们上面实现的功能完全相同。主要区别在于D,按照node.js约定,它将错误作为第一个参数传递给回调函数,并将回调作为第二个参数传递给回调函数。

使用promise,我们将其编写如下:

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

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

12
“由于它是单线程的,因此在node.js中没有什么是真正并行的。” 不对。所有不使用CPU的事物(例如等待网络I / O)都是并行运行的。
Thilo

3
在大多数情况下,这是对的。在Node中等待IO不会阻止其他代码运行,但是在代码运行时,一次只能执行一次。Node中唯一真正的并行执行来自产生子进程,但是几乎可以在任何环境中这样说。
MooGoo 2011年

6
@Thilo:通常我们将不使用CPU的代码称为未运行。如果您没有运行,则不能并行运行。
slebetman 2011年

4
@MooGoo:这意味着事件,因为我们知道它们绝对不能并行运行,因此我们不必担心信号量和互斥量,而对于线程,我们必须锁定共享资源。
slebetman 2011年

2
我是不是说这些不是并行执行的函数,但是(最多)它们是按不确定的顺序执行的,直到每个“ async_func”返回时代码才继续执行?
亚伦·鲁斯塔德

10

我相信现在“异步”模块提供了此并行功能,并且与上面的fork函数大致相同。


2
这是不正确的,异步仅可帮助您在单个过程中组织代码流。
bwindels

2
async.parallel确实确实执行了与上述fork功能相同的操作
Dave Stibrany

这不是真正的并行性
rab

5

期货模块有一个名为子模块加入,我喜欢使用:

将异步调用连接在一起,类似于pthread_join线程的工作方式。

自述文件显示了一些使用自由样式或使用Promise模式使用将来的子模块的良好示例。来自文档的示例:

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);

2

这里可能有一个简单的解决方案:http : //howtonode.org/control-flow-part-ii滚动到“并行操作”。另一种方法是让A,B和C共享同一个回调函数,让该函数具有全局或至少不在功能范围内的增量器,如果这三个函数均已调用了回调,则让其运行D,当然,您还必须将A,B和C的结果存储在某个位置。




By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.