如何并行运行两个 setTimeout 任务?

How to run two setTimeout tasks in parallel?

提问人:Armeen Moon 提问时间:4/13/2020 最后编辑:Armeen Moon 更新时间:4/13/2020 访问量:994

问:

我正在阅读 YDKJS,早期我们正在讨论异步代码、并行代码和并发代码之间的区别。

我有一个简单的异步示例:

let output = 0;
const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);

bar( () => {
    output = 1;
});
foo( () => {
    output = 2
});
setTimeout(() => {
    // This Async code should have 2 different outputs
    output;
}, 2000);

上面的代码可以有基于 Math.random 计时器和可变输出的 2 个答案:

但是,我想增加一点复杂性,并将 foo 和 bar 转换为并行运行......我对如何实现这一目标不太了解:

问题:我们如何更新下面的代码,以便并行运行,因此输出具有 2 个以上可能的结果?barfoo

注意:这纯粹是出于学习目的......我想看到比赛条件的发生。

let inputA = 10;
let inputB = 11;

const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);


bar( () => {
    inputA++;
    inputB = inputB * inputA;
    inputA = inputA + 3;
});
foo( () => {
    inputB--;
    inputA = 8 + inputB;
    inputB =  inputA * 2;
});
setTimeout(() => {
    // This Parallel code should have more than 2 outputs;
    console.log(inputA, inputB);
}, 2000);
JavaScript的 并行处理 可变

评论

2赞 CertainPerformance 4/13/2020
推送到数组而不是重新分配变量?
0赞 Armeen Moon 4/13/2020
我想我应该更明确地说,这纯粹是为了学习,我希望创造比赛条件。我已经更新了这个问题,希望它能更好地传达。
0赞 CertainPerformance 4/13/2020
您的意思是,您想看看当回调与 同时运行时会发生什么,以便输入的重新分配干扰另一个函数的运行 - 而不是一个函数完成,然后另一个函数启动?barfoo
0赞 Armeen Moon 4/13/2020
是的,没错。

答:

0赞 Colin 4/13/2020 #1

对于您的原始问题,您希望看到它们并行运行。您可以使用 Promise.all 运行多个异步任务,它将等待所有异步任务解析并返回一个输出数组。

Promise.all 通过迭代(串联)来执行异步任务,从技术上讲不是并行执行它们,而是并行运行。当所有异步任务都解决或拒绝时,如果其中任何一个任务失败,它将为您提供结果。

或者,您可以在没有 Promise.all 的情况下逐个运行它们。它们不会相互阻塞,所以仍然是并行的,但你 Promise.all 只是帮助你在一个地方处理回调结果。

输出将为 12 或 20,具体取决于您为 bar 和 foo 函数设置的随机超时。

对于争用条件,只有 setTimeout 函数是异步的,但回调中的所有操作都是同步且非阻塞的,因此线程不会从一个回调中的操作跳转到另一个回调,除非该回调中的所有操作都已完成。

但是在 JS 中,使用 SharedArrayBuffer 时仍然可以进行数据争用,这需要 Atomics 对象来防止数据争用。

let output = 0;
let inputA = 10;
let inputB = 11;

const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);

bar( () => {
    inputA++;
    inputB = inputA;
    output = inputA + 1;
});
foo( () => {
    inputB--;
    inputA = inputB;
    output =  inputB * 2;
});


Promise.all([bar(),foo()])
.then(output => console.log('foo and bar tasks finished with output ',output));

setTimeout(() => {
    console.log('output variable value: ', output)
}, 2000);

评论

1赞 Rajesh 4/13/2020
请添加解释。你改变了什么?你为什么变了?它如何帮助读者并解决 OP 的问题?
1赞 Armeen Moon 4/13/2020
这纯粹是出于学习目的。我希望有比赛条件。在此示例中,我将始终得到一个 [4, 5] 的数组
0赞 Colin 4/13/2020
输出是针对 Promise.all 的,它是 bar 和 foo 的结果。我在 2 秒后向控制台注销添加了一个新输出,它是由 foo 和 bar 函数更改的输出。
0赞 CertainPerformance 4/13/2020 #2

幸运的是,您要调用的竞争条件在普通 Javascript 中是不可能的。任何时候,只要你有一个同步函数,一旦控制流被传递给该函数,该函数就绝对保证在环境中的任何其他 Javascript 运行之前运行到最后(或抛出)。

例如,给定 1000 个任务,这些任务计划在接下来的 1-2 秒内(随机)发生,并且您还在 1.5 秒后调用以下函数:setTimeout

const fn = () => {
  console.log('1');
  for (let i = 0; i < 1e8; i++) {
  }
  console.log('2');
};

启动后,它将在下一个随机函数运行之前(同步)运行其所有代码。所以即使随机函数调用,仍然可以保证,在上述情况下,会立即被记录下来。fnconsole.log21

因此,在原始示例中,只有 2 种可能性:

  • bar的回调首先运行,并完全完成,然后 的回调运行,并完全完成。或:foo
  • foo的回调首先运行,并完全完成,然后 的回调运行,并完全完成。bar

即使随机超时落在完全相同的数字上,也不可能有其他可能。在任何给定时间,控制流只能位于代码中的一个位置

评论

0赞 Armeen Moon 4/13/2020
github.com/KBPsystem777/You-Dont-Know-JS/blob/master/......但是,如果共享相同数据的 JS 事件并行执行,问题会更加微妙。将这两个伪代码任务列表视为可以分别在 foo() 和 bar() 中运行代码的线程,并考虑如果它们以完全相同的 ti 运行会发生什么
0赞 Armeen Moon 4/13/2020
我刚刚重读了哈哈——JavaScript 从不跨线程共享数据,这意味着非确定性水平不是问题。
0赞 Armeen Moon 4/13/2020
那么在 JavaScript 中没有真正的(完全)并行性吗?
2赞 CertainPerformance 4/13/2020
除非一个函数调用另一个函数,否则两个函数不能同时运行。虽然有一些 Web Worker(和其他主机提供的机制),由于是两个独立的环境,它们可以运行单独的 JS,但这种事情超出了标准的纯 JS。
0赞 Colin 4/13/2020
仅供参考,JS 确实在线程、代理之间共享一些数据。如果非确定性是一个问题,则存在类似于 JS 中的竞争条件的情况。