最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin1浏览0评论

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.

发布评论

评论列表(0)

  1. 暂无评论