It took me a while to understand how NodeJS achieves non-blocking IO within one single thread. The misconception about NodeJS being single-threaded is what causes my confusion. In this post, I’ll demonstrate that NodeJS is not completely single-threaded, and show you how event loops work.
Consider this piece of code:
When the above code gets executed, all the function calls enter into the event loop. After running the code, you’ll find that these 3 function calls take almost the same amount of time. Remember that the event loop is in one single thread? How in the world that Node manages to run 3 operations in parallel within one single thread?
The behavior we observed earlier was because Node implements a C module called
libuv. Whenever a long running operation takes place in the event loop,
libuv will come to help and put that task into another thread. When the operation finishes, the event loop will trigger the callback to handle the result.
Now, increase the
crypto.pbkdf2() function call to 5 times, and see what happens.
You will find that the first 4 calls take almost the same amount of time, while the last takes double. Here we encounter an interesting part of Node. By default, the
libuv library initiates 4 threads in something called “thread pool”. The 4 threads run in parallel, that’s why the 4 operations take the same amount of time. Because the thread pool can only take 4 operations in a time, the fifth operation just waits for previous operations to finish. That’s why the fifth operation takes longer.
Now, let’s change the type of operations. This time we will make http calls in parallel and see what happens.
You’ll find that these 5 calls take almost the same amount of time. We can reasonably infer that these http calls happen neither in the single-threaded event loop nor in the thread pool. So, what happened?
It turns out that some low level OS tasks like http requests are delegated to the operating system by
libuv. These tasks get executed in parallel outside of event loop and the thread pool.
The event loop also handles timer events, i.e.
setImmediate. When a timer event is registered, the event loop will wait specified amount of time and trigger the callback.
In summary, the event loop keeps track of these three kinds of events:
Here is a diagram I drew to illustrate the big picture: