Is increment an atomic operation in JavaScript? If one thread is accessing
++i;
and at the same time another one starts to access the operation will there be any problems?
In JavaScript, a function always runs to completion. That means if a function is running, then it will run completely; only after that, the other function will be called. So, there is no chance of interleaving between statements (but in case of Java it is different).
If you are confused with asynchronous execution, then always remember async means later not parallel. So, coming to your problem, the answer is, No you will not face any problem, it will be a total atomic operation.
x = 1; async function foo() { x += await 1; }
will pause at the await
before completing and finish the rest (the actual assignment) on a subsequent cycle. This can lead to unexpected results: jsbin.com/zufozat/1/edit?js,console Similarly, generator functions do not necessarily run to completion - a yield
will pause their execution until a next value is requested when they'd run to the next yield
etc. –
Leven If one thread is accessing
++i
; and at the same time another one starts to access the operation will there be any problems?
That won't happen with a simple variable like i
, because JavaScript is defined such that there can only be one active thread (the agent's executing thread) in a realm at any given time. ("realm" - roughly speaking, a JavaScript global environment and the things within it, such as your variables.) So the issue simply doesn't arise with normal variables or object properties. Your function can't be interrupted during synchronous operation; JavaScript defines "run to completion" semantics: whenever a "job" (like triggering an event handler) is picked up from the job queue and executed, it runs to completion before any other job can be executed. (For an async
function, the logic can only be suspended at await
, return
, or throw
, not in the middle of a synchronous compound arithmetic operation. It can be in the middle of a compound arithmetic operation involving await
. More about this below. Similarly, for generator functions, their logic is suspended at yield
.)
The only place you'd have to worry about this is if you're using shared memory, where the actual memory is shared between realms and thus could indeed be accessed by multiple threads at the same time. But if you were doing that, you'd be dealing with a SharedArrayBuffer
or a typed array using a SharedArrayBuffer
, not a simple variable or property. But yes, if dealing with shared memory, you're exposed to all the "glorious" fun of CPU operation reordering, stale caches, and so on. That's part of why we have the Atomics
object, including Atomics.add
, which atomically adds a value to an element in a typed array using shared memory. (But beware naïve usage! After all, another thread could overwrite the value just after your add
finishes, before you read it... Which is why Atomics.add
returns the new value, which is what you should use instead.) Atomics
provides the bare building blocks necessary to ensure safe access to shared memory. (More about this in Chapter 16 of my book JavaScript: The New Toys: "Shared Memory and Atomics
".)
Note: This all applies to standard JavaScript engines that comply with the specification, such as those found in web browsers and Node.js. Non-standard JavaScript environments, such as the the scripting support for JavaScript built into the Java Virtual Machine, may (of course) define alternative non-standard semantics.
Re async
functions: There is no multithreading involved in async
functions. But the fact the logic of the function is suspended at an await
can cause some surprising behavior. That said, the place it can occur is clearly flagged with the await
.
I wouldn't worry about the details below unless you have to. But for those who do...
Consider:
let a = 1;
async function one() {
return 1;
}
async function example() {
console.log(`Adding 1 to a`);
a += await one();
}
console.log(`Start, a = ${a}`);
Promise.all([
example(),
example(),
example(),
])
.then(() => {
console.log(`All done, a = ${a}`);
});
(Technically, we could just use a += await 1;
, because await
will wrap its operand in an implied Promise.resolve(x)
, but I thought it would be clearer to show an actual promise.)
That outputs:
Start, a = 1 Adding 1 to a Adding 1 to a Adding 1 to a All done, a = 2
But wait, we added 1 to a
three times, it should have ended up being 4, not 2?!?!
The key is in the await
in this statement:
a += await one();
That's processed like this:
- Get the current value of
a
and set it aside; call itatemp
. - Call
one
and get its promise. - Wait for the promise to settle and get the fulfillment value; let's call that value
addend
. - Evaluate
atemp + addend
. - Write the result to
a
.
Or in code:
/* 1 */ const atemp = a;
/* 2 */ const promise = one();
/* 3 */ const addend = await promise; // Logic is suspended here and resumed later
/* 4 */ const result = atemp + addend;
/* 5 */ a = result;
(You can find this detail in EvaluateStringOrNumericBinaryExpression in the spec.)
Where we used example
, we called it three times without waiting for its promise to settle, so Step 1 was run three times, setting aside the value of a
(1) three times. Then later, those saved values are used and a
's value is overwritten.
Generator functions have a similar behavior at yield
, where their logic is suspended and then resumed later.
Again, there was no multithreading involved (and run-to-completion is fully intact), it's just that when an async
function's logic reaches an await
, return
(explicit or implicit), or throw
, the function exits at that point and returns a promise. If that was because of an await
, the function's logic will continue when the promise the function await
ed settles, and will (in the normal course of things) eventually settle the promise the async
fucntion returned. But each of those things will happen to completion, and on a single active thread.
Atomics.add
and then reading a different value: that's why atomic ops return a value, which as you hint at you should use instead of reading the shared variable again separate from the atomic RMW. Thanks for explaining why JS has atomics when it normally doesn't need them. –
Squadron Javascript is single threaded, So you need to worry about deallocks or dirty read problems. Why doesn't JavaScript support multithreading?
Javascript does not support multithreading. It may have web workers, but your question would not apply to this case, as workers do not share variables.
Yes there will be a problem. Even if Javascript is single threaded, i++ is (read + modify + write) 3 steps operation, So when anyone is taking the i variable from asyncfunction, the other one can set the modified i to the variable area. In order to solve this issue, you can use atomic variables instead of regular variable. When one process take the number with read another process can change the number with write during the first one modifying. No matter this is single thread or multithread, all the things happen the same. All the things I said here are retated to asyncfunction on Node.js.
i
at the same time than other code is modifying it ? You need threads to access the same variable at the same time. –
Quezada node.js
processes accessing some shared memory? I didn't know JS could use shared memory of IPC, but if it can, then yes atomicity would become a concern. –
Squadron a
that result in values in i
that show a non-atomic increment. But a
always increments i
effectively atomically, because of JavaScript's run-to-completion and single-active-thread semantics. –
Maximilianus © 2022 - 2024 — McMap. All rights reserved.