JavaScript 中的竞争条件与复合赋值
Posted
技术标签:
【中文标题】JavaScript 中的竞争条件与复合赋值【英文标题】:Race condition in JavaScript with compound assignment 【发布时间】:2017-11-04 09:35:52 【问题描述】:我不是在谈论复杂的比赛条件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!
【问题讨论】:
我很好奇实现是如何在幕后进行的,也许在 await 语句期间创建了一个闭包,该语句存储了 await 调用时当前范围内的所有变量。有趣的是,这两个函数似乎完全独立地增加了 acc,就像它们将 acc 放在本地范围而不是全局范围中一样 【参考方案1】:确实,将acc += await n(c)
行替换为:
const ret = await n(c); acc += ret;
避免了竞争条件。
我的猜测是 V8 并没有将 n()
结果的 ADD 优化为包含 acc
的内存位置上的 acc += await n(c)
,而是将其扩展为 acc = acc + await n(c);
,以及 @987654328 的初始值@nForever(5.3)
首次调用时为 0。
这对我来说是违反直觉的,尽管不确定 V8 开发人员是否会认为这是一个错误。
【讨论】:
有趣的是,如果我通过 babeljs 网站转换您的原始代码,它实际上会按照您的预期工作,在 5.3 秒后给出 10.3【参考方案2】:这不是竞争条件,因为您明确使用await
让步执行。
该标准定义复合赋值(如+=
)不是原子的:复合赋值的左侧在右侧之前被评估。[1]
因此,如果您的 RHS 以某种方式更改 acc
,则更改将被覆盖。最简单的例子:
var n = 1;
n += (function ()
n = 2;
return 0;
)();
console.log(n);
【讨论】:
感谢您指出标准。它表明我的 assembly language driven mental model of the compound assignment 不正确(准确地说,被第 5 步破坏了)。以上是关于JavaScript 中的竞争条件与复合赋值的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Java 没有条件与和条件或运算符的复合赋值版本? (&&=, ||=)