Javascript is single threaded and – Node.js uses an asynchronous event-driven design pattern, which means that multiple actions are taken at the same time while executing a program.
With this in mind, I have a pseudo code:
myFunction() // main flow
var httpCallMade = false // a global variable
async myFunction() {
const someData = await callDB() // LINE 1 network call
renderMethod() // LINE 2 flow1
}
redisPubSubEventHandler() { // a method that is called from redis subscription asynchronously somewhere from a background task in the program
renderMethod() // LINE 3 flow2
}
renderMethod(){
if(!httpCallMade) {
httpCallMade = true //set a global flag
const res = makeHTTPCall() // an asynchronous network call. returns a promise.
} // I want to ensure that this block is "synchronized" and is not acessible by flow1 and flow2 simultaneously!
}
myFunction()
is called in the main thread – while redisPubSubEventHandler()
is called asynchronously from a background task in the program. Both flows would end in calling renderMethod(). The idea is to ensure makeHTTPCall()
(inside renderMethod
) is only allowed to be called once
Is it guaranteed that renderMethod() would never be executed in parallel by LINE2 and LINE3 at the same time? My understanding is that as soon as renderMethod() is executed – event loop will not allow anything else to happen in server – which guarantees that it is only executed once at a given time (even if it had a network call without await).
Is this understanding correct?
If not, how do I make synchronize/lock entry to renderMethod
?
4
Answers
You need to differentiate between synchronous and asynchronous, and single- and multi-threaded.
JavaScript is single-threaded so no two lines of the same execution context can run at the same time.
But JavaScript allows asynchronous code execution (
await
/async
), so the code in the execution context does not need to be in the order it appears in the code but that different parts of the code can be executed interleaved (not overlapped) – which could be called "running in parallel", even so, I think this is misleading.There are certain actions that can happen at the same time, like IO, multiprocessing (WebWorkers), but that is (with respect to JavaScript Code execution) not multi-threaded.
Depends on what you define as parallel at the same time.
Parts of logic you describe in
renderMethod()
will (as you do the request asynchronously) run interleaved, sorenderMethod(){ if(!httpCallMade) {
could be executed multiple times before you get the response (not the Promise) back frommakeHTTPCall
but the code lines will never executed at the same time.The problem here is, that you somehow need to get the data from your async response.
Therefore you either need to mark your function as
async
and useconst res = await makeHTTPCall()
this would allow code interleaving at the point ofawait
. Or use.then(…)
with a callback, which will be executed asynchronously at a later point (after you left the function)But from the beginning of the function to the first
await
other the.then
not interleaving could take place.So your
httpCallMade = true
would prevent that anothermakeHTTPCall
could take place, before the currently running is finished, under the assumption that you sethttpCallMade
tofalse
only when the request is finished (in.then
callback, or after theawait
)As soon as a get a result in an asynchronous way, you can’t go back to synchronous code execution. So you need to have a guard like
httpCallMade
to prevent that the logic described inrenderMethod
can run multiple times interleaved.It sounds like you want to do something like a ‘debounce’, where any event will cause
makeHttpCall()
execute, but it should only be executing once at a time, and should execute again after the last call if another event has occurred while it was executing. So like this:makeHttpCall()
should executemakeHttpCall()
is executing, you get a redis pub/sub event that should execute makeHttpCall() again, but that is delayed because it is already executingmakeHttpCall()
to execute again. But even though you have received two events, you only need to have it called one time to update something with the most recent information you have.makeHttpCall()
finishes, but since there have been two events, you need to make a call again.Your question really comes down to:
Given this code:
and considering that
flag
is not modified anywhere else, and many different, asynchronous events may call this functionf
…:Can "hello" be printed twice?
The answer is no: if this runs on an ECMAScript compliant JS engine, then the call stack must be empty first before the next job is pulled from an event/job queue. Asynchronous tasks/reactions are pushed on an event queue. They don’t execute before the currently executing JavaScript has run to completion, i.e. up until the call stack is empty. So they never interrupt running JavaScript code pre-emptively.
This is true even if these asynchronous tasks/events/jobs are scheduled by other threads, lower-level non-JS code,…etc. They all must wait their turn to be consumed by the JS engine. And this will happen one after the other.
For more information, see the ECMAScript specification on "Job". For instance 8.4 Jobs and Host Operations to Enqueue Jobs:
For example, promises generate such jobs — See 25.6.1.3.2 Promise Resolve Functions:
Javascript is single-threaded. Therefore, unless you are deliberately using threads (eg. worker_threads in node.js) no function in the current thread can be executed by two parallel threads at the same time.
This explains why javascript has no mutex or semaphore capability – because generally it is not needed (note: you can still have race conditions because asynchronous code may be executed in a sequence you did not expect).
There is a general confusion that asynchronous code means parallel code execution (multi-threaded). It can but most of the time when a system is labeled as asynchronous or non-blocking or event-oriented INSTEAD of multi-threaded it often means that the system is single-threaded.
In this case asynchronous means parallel WAIT. Not parallel code execution. Code is always executed sequentially – only, due to the ability of waiting in parallel you may not always know the sequence the code is executed in.
There are parts of javascript that execute in a separate thread. Modern browsers execute each tab and iframe in its own thread (but each tab or iframe are themselves single-threaded). But script cannot cross tabs, windows or iframes. So this is a non-issue. Script may access objects inside iframes but this is done via an API and the script itself cannot execute in the foreign iframe.
Node.js and some browsers also do DNS queries in a separate thread because there is no standardized cross-platform non-blocking API for DNS queries. But this is C code and not your javascript code. Your only interaction with this kind of multi-threading is when you pass a URL to
fetch()
orXMLHttpRequest()
.Node.js also implement file I/O, zip compression and cryptographic functions in separate threads but again this is C code, not your javascript code. All results from these separate threads are returned to you asynchronously via the event loop so by the time your javascript code process the result we are back to executing sequentially in the main thread.
Finally both node.js and browsers have worker APIs (web workers for browsers and worker threads for node.js). However, both these API use message passing to transfer data (in node only a pointer is passed in the message thus the underlying memory is shared) and it still protects functions from having their variables overwritten by another thread.
In your code, both myFunction() and redisPubSubEventHandler() run in the main thread. It works like this:
myFunction() is called, it returns immediately when it encounters the
await
.a bunch of functions are declared and compiled.
we reach the end of your script:
now that we have reached the end of script we enter the event loop…
either the
callDB
or the redis event completes, our process gets woken upthe event loop figures out which handler to call based on what event happened
either the
await
returns and callrenderMethod()
orredisPubSubEventHandler()
gets executed and callrenderMethod()
In either case both your
renderMethod()
calls will execute on the main thread. Thus it is impossible forrenderMethod()
to run in parallel.It is possible for
renderMethod()
to be half executed and another call torenderMethod()
happens IF it contains theawait
keyword. This is because the first call is suspended at theawait
allowing the interpreter to callrenderMethod()
again before the first call completes. But note that even in this case you are only in trouble if you have anawait
betweenif..
andhttpCallMade = true
.