I'm not talking about plex race conditions involving the network or events. Rather, I seem to have found out that the +=
operator is not atomic in V8 (Chrome 58, or Node 8).
The code below aims to run two so-called threads in parallel. Each "thread" calls repeatedly a function that returns its number parameter after sleeping that many seconds. The results are summed up into an accumulator.
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
})();
I'm not talking about plex race conditions involving the network or events. Rather, I seem to have found out that the +=
operator is not atomic in V8 (Chrome 58, or Node 8).
The code below aims to run two so-called threads in parallel. Each "thread" calls repeatedly a function that returns its number parameter after sleeping that many seconds. The results are summed up into an accumulator.
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
})();
The problem is that after ~5 seconds, I'd expect the accumulator to be 10.3 (5 times 1 + 1 times 5.3). However, it's 5.3!
Share Improve this question edited Jun 2, 2017 at 22:27 Kijewski 26k14 gold badges107 silver badges147 bronze badges asked Jun 2, 2017 at 21:58 Dan DascalescuDan Dascalescu 152k65 gold badges333 silver badges420 bronze badges 1- 1 I'm curious how the implementation is behind the scenes, maybe a closure is created during an await statement that stores all variables that are currently in scope at the time of the await call. It is quite interresting to see that both functions seem to increase the acc pletely independently, like they put acc in their local scope instead of the global one – Icepickle Commented Jun 2, 2017 at 22:12
2 Answers
Reset to default 7This is not a race condition, because you are explicitly yielding the execution using await
.
The standard defines that a pound assignment such as +=
is not atomic: The left-hand-side of a pound assignment is evaluated before the right-hand-side.[1]
So if your RHS changes acc
somehow, the changes will be overwritten. Most simple example:
var n = 1;
n += (function () {
n = 2;
return 0;
})();
console.log(n);
Indeed, after replacing the acc += await n(c)
line with:
const ret = await n(c); acc += ret;
the race condition was avoided.
My guess is V8 didn't optimize acc += await n(c)
to an ADD of the n()
result over the memory location containing acc
, but rather expanded it to acc = acc + await n(c);
, and the initial value of acc
when nForever(5.3)
was first called, was 0.
This is counter-intuitive to me, though not sure the V8 developers would consider it a bug.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744756876a4591934.html
评论列表(0条)