0

I want to know how can I ensure that I increment counter correctly in Node.js environment.

If I understand correctly, async functions are processed by non-main threads that are instantiated by 4 libuv library threads, and the callbacks are executed by the main thread (event loop).

If above is true, then below code can result in incorrect updates to counter because f1() & f2() can try to update counter at the same time giving a final result of 1 instead of 2.

How do I rewrite the code below to ensure correct update to counter?

var counter = 0

async function f1() {
  // do something
  
  counter++
}

async function f2() {
  // do something
  
  counter++
}
f1()
f2()
Vishal
  • 111
  • 6
  • Maybe this: [How to lock on object which shared by multiple async method in nodejs?](https://stackoverflow.com/questions/38802959/how-to-lock-on-object-which-shared-by-multiple-async-method-in-nodejs) ?? – Luuk Jun 25 '23 at 13:05
  • @LinDu There is no guarantee that f1 will be called before f2. – Vishal Jun 25 '23 at 13:08
  • @Luuk cannot use 3rd party libraries. Only vanilla APIs. – Vishal Jun 25 '23 at 13:09
  • What do you call "a final result"? No, there is no way that `f1` and `f2` access the same `counter` variable at the same time, so the "final" result will be `2`. However which of `f1` or `f2` would have set it to `1` or `2` is unsure. Async funtions are still ran by the one and only JS processing. You'd have such issues only with `SharedArrayBuffer` objects, but for these, we have `Atomics`. – Kaiido Jun 25 '23 at 13:29
  • @Kaiido That is what I want to clarify. Can you explain why `f1()` & `f2()`cannot access counter variable at the same time if they are being processed by background threads? The final result in this case is expected to be `2` – Vishal Jun 25 '23 at 13:31
  • "Async fun[c]tions are still ran by the one and only JS processing." They're not processed by background threads. Maybe they're themselves waiting for some tasks that are processed by other processes, but the JS part is ran by the one and only JS processing that has access to `counter`. So there is no risk of clashing here. Once again, the only case where you could have such an issue is with `SharedArrayBuffer` shared across different JS contexts (e.g with web workers). Here `counter` is a number and can't be shared across contexts. – Kaiido Jun 25 '23 at 14:41
  • @Kaiido When do libuv threads come into picture if async functions are processed by main threads? – Vishal Jun 25 '23 at 14:45
  • When you do something like `fs.readFile(url, cb)`, the filesystem io will be handled by another thread, but the callback `cb` will be ran by the one and only JS processing. That's just the same with `async` functions. Whatever API uses another thread will still use that other thread, but the JS execution will get ran by the JS processing. – Kaiido Jun 25 '23 at 14:53

2 Answers2

2

No, there is no way that f1 and f2 do access the same counter variable at the same time, so the "final" result will be 2.
However which of f1 or f2 would have set it to 1 or 2 is unsure.

Async functions are still ran by the one and only JS processing, not by background threads. Maybe they will themselves be waiting for some tasks that are processed by other processes, depending on the API calls they'll make, but the JS part is still ran by the one and only JS processing that has access to the counter variable.

So there is no risk of clashing here.

The only case where you could have such an issue is with SharedArrayBuffer objects, when shared across different JS contexts (e.g with web workers). Though for this case we have Atomics.
But here the counter variable is not a SharedArrayBuffer, and can't be shared across contexts. So once again, when f1 and f2 will both have resolved in an undetermined order, counter will hold the value 2.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
0

If I understand correctly, async functions are processed by non-main threads

This is not true. All async functions are processed in the main thread. Therefore calling f1() and f2() will always result in counter being incremented twice. It will never fail. This is because javascript is single threaded.

It is important to know that javascript is single threaded. Even operations that are performed in multiple threads by Node.js such as calling web workers or performing file I/O are serialized back to the main thread by the event loop.

Unless you're using non-standard 3rd party threading libraries variable access in node.js are never simultaneous. This is because variables cannot be shared between workers in node.js (that is, if you use worker_threads - in normal coding you're not even executing your own code in separate threads). Data is exchanged between threads via messages - similar to the way client and servers on the internet share data with each other: they never access each other's variables.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • YOu mean the callbacks are serialized to the main thread, not the I/O call itself. Right? – Vishal Jun 26 '23 at 04:32
  • Yes. The code that runs on the threads are C++ code written by node developers. All your own code from requesting the read to the callback are handled in the main thread. The event loop is basically a giant state machine – slebetman Jun 26 '23 at 05:10
  • For worker_threads you execute your own code in the other threads but the threads don't share variables. They execute in their own javascript interpreters. They can communicate the usual ways using sockets or pipes though the API give you high level functions to pass messages between threads that handles the low level details for you – slebetman Jun 26 '23 at 05:16
  • Requesting reads and other I/O operations are handled by background threads, not main thread. – Vishal Jun 26 '23 at 05:32
  • The C code requesting the read are done in the background thread. However, the code you typed (eg: `fs.readFile(...)`) is executed in the main thread. – slebetman Jun 26 '23 at 05:33
  • Yup, thats what I said – Vishal Jun 26 '23 at 05:57
  • Do note though that this applies only for file I/O. Network I/O are handled by the main thread. One way to think about it is that node cannot run code in parallel but can wait for events in parallel. – slebetman Jun 26 '23 at 05:59
  • A language like JS is neither multi or single threaded, that doesn't mean anything. If anything, it does acknowledge that it can be running in a multi-threaded environment since it does provide [Atomics operations](https://tc39.es/ecma262/multipage/structured-data.html#sec-atomics-object). – Kaiido Jun 26 '23 at 06:01
  • No network & disk IO both are background threads. You will find this helpful: https://www.geeksforgeeks.org/libuv-in-node-js/ – Vishal Jun 26 '23 at 06:01
  • No. Network I/O is handled in the main thread. Look at the link you provided: https://www.geeksforgeeks.org/libuv-in-node-js. See the diagram that shows that only File I/O and DNS are handled by the thread pool while everything else is handled by the event loop of the main thread. That bit showing user code in the thread pool is probably referencing worker_threads. Reading about node.js form blogs can sometimes give you wrong information because some people don't understand it. Read the node documentation for better understanding: https://nodejs.org/en/docs/guides/dont-block-the-event-loop – slebetman Jun 26 '23 at 06:06
  • Quoting you: "See the diagram that shows....oop of the main thread". You mean the diagram titled "libuv" ? Yeah thats for background threads. They are showing that Network I/O is handled by libuv thread pool. – Vishal Jun 26 '23 at 07:12
  • libuv also handles async I/O. It's main purpose is to standardise async I/O functions across different OSes (select, kqueue, epoll etc.). It's secondary purpose is to manage thread pools. That's for the async I/O, not background threads – slebetman Jun 26 '23 at 07:30
  • Please read the definitive documentation written by node.js developers that explains that network I/O is handled in the main thread: https://nodejs.org/en/docs/guides/dont-block-the-event-loop – slebetman Jun 26 '23 at 07:30
  • Ok thank you for letting me know. – Vishal Jun 26 '23 at 08:14