15

正如我目前所了解的那样:Javascript 是单线程的。如果您推迟执行某些过程,您只需安排它(排队)在线程空闲时运行。但是 Async.js 定义了两个方法:Async::parallel & Async::parallelLimit,我引用:

  • 并行(任务,[回调])

并行运行一组函数,无需等到前一个函数完成。如果任何函数将错误传递给其回调...

  • 并行限制(任务,限制,[回调])

与并行相同,仅任务是并行执行的,在任何时候执行的最大“限制”任务。

就我对英语的理解而言,当您说:“并行执行任务”意味着同时执行它们 - 同时进行。

Async.js 如何在单线程中并行执行任务?我是不是错过了什么。

4

5 回答 5

14

Async.js 如何在单线程中并行执行任务?我是不是错过了什么。

parallel同时运行所有任务。因此,如果您的任务包含 I/O 调用(例如查询数据库),它们将看起来好像它们已被并行处理。

这是如何在单个线程中启用的?!那是我无法理解的。

Node.js 是非阻塞的。因此,它不是并行处理所有任务,而是从一个任务切换到另一个任务。因此,当第一个任务进行 I/O 调用使自己空闲时,Node.js 只是切换到处理另一个任务。

I/O 任务的大部分处理时间都在等待 I/O 调用的结果。在像 Java 这样的阻塞语言中,这样的任务在等待结果时会阻塞它的线程。但是 Node.js 利用它来处理另一个任务而不是等待。

所以这意味着如果每个任务的内部处理是异步的,那么线程被授予这个任务的每个位,无论他们中的任何一个是否已经完成,直到所有人都完成了他们的位?

是的,和你说的差不多。Node.js 开始处理第一个任务,直到它暂停执行 I/O 调用。在那一刻,Node.js 离开它并将其主线程授予另一个任务。所以你可能会说线程被依次授予每个活动任务。

于 2013-09-26T09:53:24.210 回答
4

Async.Parallel 在这里有很好的记录: https ://github.com/caolan/async#parallel

Async.Parallel 是关于并行启动 I/O 任务,而不是关于代码的并行执行。如果您的任务不使用任何计时器或执行任何 I/O,它们实际上将被串行执行。每个任务的任何同步设置部分都将一个接一个地发生。JavaScript 仍然是单线程的。

于 2015-08-10T18:21:33.960 回答
2

这些函数不会同时执行,但是当第一个函数移交给异步任务(例如 setTimeout、network、...)时,即使第一个函数没有调用提供的回调,第二个函数也会启动。

至于并行任务的数量:这取决于您选择的内容。

于 2013-09-26T09:49:35.670 回答
1

就我对英语的理解而言,当您说:“并行执行任务”意味着同时执行它们 - 同时进行。

正确的。而“同时”的意思是“至少有一个时刻,两个或多个任务已经开始,但尚未完成”。

Async.js 如何在单线程中并行执行任务?我是不是错过了什么。

当某些任务由于某种原因(即 IO)停止时,async.js 会执行另一个任务并稍后继续第一个任务。

于 2013-09-27T07:06:23.270 回答
1

你的怀疑完全有道理。您问这个问题已经有几年了,但我认为值得对现有答案添加一些想法。

并行运行一组函数,无需等到前一个函数完成。如果任何函数将错误传递给其回调...

这句话并不完全正确。事实上,它确实会等待每个函数完成,因为在 JavaScript 中不可能不这样做。函数调用和函数返回都是同步和阻塞的。因此,当它调用任何函数时,它必须等待它返回。它不必等待的是调用传递给该函数的回调。

寓言

前段时间我写了一个短篇故事来证明这个概念:

引用其中的一部分:

“所以我说:‘等一下,你告诉我一个蛋糕需要三个半小时,而四个蛋糕只需要一个多半小时?这没有任何意义!我想她一定是在开玩笑,所以我开始大笑。”
“但她不是在开玩笑吗?”
“不,她看着我说:‘这完全有道理。这个时间主要是在等待。我可以一次等待很多事情就好了。我停止了笑,开始思考。它终于开始影响我了。同时做四个枕头并没有给你带来任何时间,也许可以说它更容易组织,但话又说回来,也许不是。但这一次,情况有所不同。但我还不知道如何使用这些知识。”</p>

理论

我认为需要强调的是,在单线程事件循环中,你一次只能做一件事情。但是你可以一次等待很多事情就好了。这就是这里发生的事情。

Async 模块中的并行函数一个一个地调用每个函数,但是每个函数都必须在调用下一个函数之前返回,没有办法绕过它。这里的神奇之处在于该函数在返回之前并没有真正完成它的工作 - 它只是安排一些任务,注册一个事件监听器,在其他地方传递一些回调,向一些承诺添加一个解析处理程序等。

然后,当计划任务完成时,执行该函数先前注册的某个处理程序,这将依次执行最初由 Async 模块传递的回调,并且 Async 模块知道该函数已完成 - 这次不仅从某种意义上说,它返回了,而且传递给它的回调最终被调用了。

例子

因此,例如,假设您有 3 个函数可以下载 3 个不同的 URL getA()getB()getC().

我们将编写一个Request模块的模拟来模拟请求和一些延迟:

function mockRequest(url, cb) {
  const delays = { A: 4000, B: 2000, C: 1000 };
  setTimeout(() => {
    cb(null, {}, 'Response ' + url);
  }, delays[url]);
};

现在这 3 个函数大部分是相同的,带有详细的日志记录:

function getA(cb) {
  console.log('getA called');
  const url = 'A';
  console.log('getA runs request');
  mockRequest(url, (err, res, body) => {
    console.log('getA calling callback');
    cb(err, body);
  });
  console.log('getA request returned');
  console.log('getA returns');
}

function getB(cb) {
  console.log('getB called');
  const url = 'B';
  console.log('getB runs request');
  mockRequest(url, (err, res, body) => {
    console.log('getB calling callback');
    cb(err, body);
  });
  console.log('getB request returned');
  console.log('getB returns');
}

function getC(cb) {
  console.log('getC called');
  const url = 'C';
  console.log('getC runs request');
  mockRequest(url, (err, res, body) => {
    console.log('getC calling callback');
    cb(err, body);
  });
  console.log('getC request returned');
  console.log('getC returns');
}

最后我们用async.parallel函数调用它们:

async.parallel([getA, getB, getC], (err, results) => {
  console.log('async.parallel callback called');
  if (err) {
    console.log('async.parallel error:', err);
  } else {
    console.log('async.parallel results:', JSON.stringify(results));
  }
});

立即显示的是:

getA called
getA runs request
getA request returned
getA returns
getB called
getB runs request
getB request returned
getB returns
getC called
getC runs request
getC request returned
getC returns

正如你所看到的,这都是顺序的——函数被一个接一个地调用,下一个在前一个返回之前不会被调用。然后我们看到这个有一些延迟:

getC calling callback
getB calling callback
getA calling callback
async.parallel callback called
async.parallel results: ["Response A","Response B","Response C"]

所以getC首先完成,然后getB-getC然后,一旦最后一个完成,async.parallel调用我们的回调,所有响应组合并以正确的顺序 - 按照我们订购函数的顺序,而不是按照那些请求完成。

我们还可以看到程序在 4.071 秒后结束,这大约是最长请求所用的时间,所以我们看到请求都在同一时间进行。

async.parallelLimit现在,让我们以最多 2 个并行任务的限制运行它:

async.parallelLimit([getA, getB, getC], 2, (err, results) => {
  console.log('async.parallel callback called');
  if (err) {
    console.log('async.parallel error:', err);
  } else {
    console.log('async.parallel results:', JSON.stringify(results));
  }
});

现在有点不同了。我们立即看到的是:

getA called
getA runs request
getA request returned
getA returns
getB called
getB runs request
getB request returned
getB returns

所以getAandgetB被调用并返回,但getC还没有被调用。然后经过一段时间的延迟,我们看到:

getB calling callback
getC called
getC runs request
getC request returned
getC returns

这表明,一旦getB调用回调,Async 模块就不再有 2 个任务正在进行,而只有 1 个,并且可以启动另一个任务,即getC,并且它会立即执行此操作。

然后我们看到另一个延迟:

getC calling callback
getA calling callback
async.parallel callback called
async.parallel results: ["Response A","Response B","Response C"]

它完成了整个过程,就像在async.parallel示例中一样。这次整个过程也花了大约 4 秒,因为延迟调用getC没有任何区别 - 它仍然设法在第一次调用完成之前getA完成。

但是,如果我们将延迟更改为那些:

const delays = { A: 4000, B: 2000, C: 3000 };

那么情况就不同了。现在async.parrallel需要 4 秒 async.parallelLimit,但限制为 2 需要 5 秒,并且顺序略有不同。

没有限制:

$ time node example.js
getA called
getA runs request
getA request returned
getA returns
getB called
getB runs request
getB request returned
getB returns
getC called
getC runs request
getC request returned
getC returns
getB calling callback
getC calling callback
getA calling callback
async.parallel callback called
async.parallel results: ["Response A","Response B","Response C"]

real    0m4.075s
user    0m0.070s
sys     0m0.009s

限制为 2:

$ time node example.js
getA called
getA runs request
getA request returned
getA returns
getB called
getB runs request
getB request returned
getB returns
getB calling callback
getC called
getC runs request
getC request returned
getC returns
getA calling callback
getC calling callback
async.parallel callback called
async.parallel results: ["Response A","Response B","Response C"]

real    0m5.075s
user    0m0.057s
sys     0m0.018s

概括

我认为要记住的最重要的事情 - 无论您使用这种情况下的回调,还是使用 Promise 或 async/await,在单线程事件循环中您一次只能做一件事,但您可以等待很多同时的事情。

于 2017-06-23T16:43:56.687 回答