node.js - Race condition in JavaScript with compound assignment - Stack Overflow

I'm not talking about plex race conditions involving the network or events. Rather, I seem to have

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
Add a ment  | 

2 Answers 2

Reset to default 7

This 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条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信