我不是在谈论复杂的种族条件involving the network或events。相反,我似乎发现+=
运算符在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!
答案 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()
acc
个acc = acc + await n(c);
结果在包含acc
的内存位置,而是将其展开为{ {1}},首次调用nForever(5.3)
时resValue "string", "chat_contact", "$arg1"
的初始值为0。
这对我来说是违反直觉的,但不确定V8开发人员会认为这是一个错误。