具有复合赋值的JavaScript中的竞争条件

时间:2017-06-02 21:58:55

标签: javascript node.js multithreading asynchronous async-await

我不是在谈论复杂的种族条件involving the networkevents。相反,我似乎发现+=运算符在V8(Chrome 58或Node 8)中不是原子的。

下面的代码旨在并行运行两个所谓的线程。每个"线程"重复调用一个函数,该函数在sleeping之后返回其数字参数很多秒。结果是summed up到累加器中。



function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Return the passed number after sleeping that many seconds
async function n(c) {
  await sleep(c * 1000);
  console.log('End', c);
  return c;
}

let acc = 0;  // global

// Call n repeatedly and sum up results
async function nForever(c) {
  while (1) {
    console.log('Calling', c);
    acc += await n(c);  // += not atomic?!
    console.log('Acc', acc);
  }
}

(async function() {
  // parallel repeated calls
  nForever(1);
  nForever(5.3);  // .3 for sanity, to avoid overlap with 1 * 5
})();




问题是在约5秒后,我预计累加器为10.3(5倍1 + 1倍5.3)。但是,它是5.3!

+= not atomic

2 个答案:

答案 0 :(得分:6)

这不是竞争条件,因为您显式使用await产生执行。

标准定义复合赋值如+=不是原子的:复合赋值的左侧在右侧之前进行求值。 [1 ]

因此,如果您的RHS以某种方式更改acc,则更改将被覆盖。最简单的例子:



var n = 1;
n += (function () {
    n = 2;
    return 0;
})();

console.log(n);




答案 1 :(得分:3)

确实,将acc += await n(c)行替换为:

const ret = await n(c); acc += ret;

避免了竞争条件。

我的猜测是,V8没有优化acc += await n(c) n() accacc = acc + await n(c);结果在包含acc的内存位置,而是将其展开为{ {1}},首次调用nForever(5.3)resValue "string", "chat_contact", "$arg1"的初始值为0。

这对我来说是违反直觉的,但不确定V8开发人员会认为这是一个错误。