Reputation: 980
I have this ultra minimal Node.js server:
http.createServer(function (req, res) {
var path = url.parse(req.url).pathname;
if (path === "/") {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('Hello World');
res.end('\n');
} else {
if (!handler.handle(path, req, res)) {
res.writeHead(404);
res.write("404");
res.end();
}
}
}).listen(port);
After doing some benchmarks I saw considerable degradation of performances under high concurrency (> 10000 concurrent connections). I started to dig deeper on Node.js concurrency and the more I search, the more I am confused...
I created a minimal example, out of the http paradigm in order to try understand things a bit better:
function sleep(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}
var t0 = performance.now();
async function init() {
await Promise.all([sleep(1000), sleep(1000), sleep(1000)]);
var t1 = performance.now();
console.log("Execution took " + (t1 - t0) + " milliseconds.")
}
init()
// Execution took 1000.299999985145 milliseconds.
From what I understand, Javascript operates on a single thread. That being said, I can't wrap my head around it acting like this:
| 1 second |
Thread #One >>>>>>>>>>>>>>
Thread #One >>>>>>>>>>>>>>
Thread #One >>>>>>>>>>>>>>
... obviously this doesn't makes sense.
So, is this a terminology problem around thread
Vs worker
with something like this:
| 1 second |
Thread #One (spawns 3 workers)
Worker #One >>>>>>>>>>>>>>
Worker #Two >>>>>>>>>>>>>>
Worker #Three >>>>>>>>>>>>>>
???
How is Node.js single-threaded but able to process three functions in parallel ?? If I am right with the parallel workers, is http
spawning multiple workers for each incoming connections ?
Upvotes: 1
Views: 800
Reputation: 1073968
A thread in a JavaScript program works by servicing an task (event) / job queue. Conceptually, it's a loop: Pick up a job from the queue, run that job to completion, pick up the next job, run that job to completion.
With that in mind, your example with promises works like this:
var t0 = performance.now();
sleep(1000)
, which
sleep(1000)
twice more. Now there are three promises, and three timer callbacks scheduled for roughly the same time.Promise.all
on those promises. This saves the state of the async function and returns a promise (which nothing uses, because nothing is using the return value of init
).sleep
promise.then
handler) rather than a "macrotask" (standard job). Modern JavaScript engines handle promise jobs at the end of the standard job that queues them (even if there's another standard job already in the main queue waiting to be done — promise jobs jump the main queue). So:
sleep
promise queues a promise job.Promise.all
's logic that stores the fulfillment value and checks to see if all of the promises it's waiting for are settled. In this case, they aren't (two are still outstanding), so there's nothing further to do and the promise job is complete.sleep
promise, queuing a promise job to run its fulfillment handlerPromise.all
's logic, which stores the fulfillment value and checks if all the promises are settled. They aren't, so it just returns.sleep
promise, queuing a promise job for its fulfillment handler.Promise.all
's logic, which stores the fulfillment result, sees that all of the promises are settled, and queues a promise job to run its fulfillment handler.init
function:
init
function does var t1 = performance.now();
, and shows the difference between t1
and t0
, which is roughly one second.There's only one JavaScript thread involved (in that code). It's running a loop, servicing jobs in the queue. There may be a separate thread for the timers, or the main thread may just be checking the timer list between jobs. (I'd have to dig into the Node.js code to know which, but I suspect the latter because I suspect they're using OS-specific scheduling features.) If it were I/O completions instead of timers, I'm fairly sure those are handled by a separate non-JavaScript thread which responds to completions from the OS and queues jobs for the JavaScript thread.
Upvotes: 3