Skip to main content

Event Loop in Node.js - Concurrency Model

In the last article, I mentioned that the Node.js uses the event-loop and callback mechanism to lift off I/O operations from the javascript's thread to the system's kernel. And in this article, we are going to understand how this actually happens.

Node.js is a single-threaded system and one thread can do only one thing at a time. So, if we have 1000 concurrent users requesting our web server then for the single-threaded system designed in the traditional approach will result in serving one request at a time and the other users will have to wait till their execution is taken up from the wait queue. This problem demands a better solution. Let's take an example to analyze and then evolve a better solution.
Consider this example; the Node.js thread is given the following tasks:
  1. Task1 - Read a file.
  2. Task2 - Request another HTTP server/service
  3. Task3 - Perform some CPU intensive calculations.
If we have a single thread that simply performs the tasks given to it then this task execution will be serial. Task2 will be blocked by Task1 although the thread is sitting idle while the OS is making the filesystem operation.
If I am able to read the file and make network calls at the same time then this is called a concurrent execution. We can make the result returned when the task is completed at a later point of time resulting in asynchronous design.
If I have to convert a single thread so that we have to make non-blocking calls then we need to change the structure in which we do our task.
So, we will utilize the single thread of the Node.js not to complete the task but to accomplish it.
What is the difference between complete and accomplish?'
Let's understand, most of the kernel or the operating system on which we work, whether its Linux, Mac OS, or Windows have async handlers, i.e. we can ask them to do something and they will provide the result in a callback.
The operating system has many threads, using which it helps to access the different system resources. OS can help to access the file in a different thread and similarly network call in a different thread.
In the above diagram, we can find that the main thread (Node single thread) is responsible for delegating the tasks to OS and then operate on the result received in the callbacks. This way it is not actually performing the tasks but getting it done by OS threads. This design now becomes really efficient and performant.
Now, we saw that we can use OS threads to perform our tasks but what about the CPU intensive tasks?
These can not be delegated to the OS due to a lack of such task handling. So, the main thread will have to execute them. It will keep the main thread busy and will make the other operations in the waiting state. Node.js solves this problem using Thread Pool.
Node.js provides Thread Pool which by default has 4 threads (can be changed with the environment variable). They are used to perform asynchronous tasks which cannot be performed by the OS.
Now, all tasks 1,2, and 3 can be performed independently or concurrently and the main thread will still be responsive to any other task delegation.
This design is implemented in Node.js through Event Loop. One of the things that the event loop has is called phases.
Note: Here we have taken few phases for the explanation. There are other phases as well.
Each phase has a callback queue associated with them. For Example, When we call setTimeout then a callback is added in the callback queue of the Timer phase.
If we have to get some I/O operation performed then it will be enqueued in the callback queue of the poll phase. OS will pick the task from this phase and after the task's completion, it will enqueue the result callback in this poll phase.
The event loop gets into one phase at a time, traverses the callback queue and executes the callbacks. It then moves to the next phase, either when all the callbacks have been executed or the limit we have specified for the callbacks. i.e. we can say if 50 callbacks are executed then move to the next phase and leave the remaining callbacks pending till that phase is reached again.
Event loop makes the Node.js async operation really fast using OS threads and thread pool. The application itself remains really performant with the main thread free to handle the task requests.
  • I hope you will now appreciate the event loop design.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...