Two techniques to prevent blocking the Event Loop when handling CPU-intensive tasks

Two techniques to prevent blocking the Event Loop when handling CPU-intensive tasks

Daily short news for you
  • For a long time, I have been thinking about how to increase brand presence, as well as users for the blog. After much contemplation, it seems the only way is to share on social media or hope they seek it out, until...

    Wearing this shirt means no more worries about traffic jams, the more crowded it gets, the more fun it is because hundreds of eyes are watching 🤓

    (It really works, you know 🤭)

    » Read more
  • A cycle of developing many projects is quite interesting. Summarized in 3 steps: See something complex -> Simplify it -> Add features until it becomes complex again... -> Back to a new loop.

    Why is that? Let me give you 2 examples to illustrate.

    Markdown was created with the aim of producing a plain text format that is "easy to write, easy to read, and easy to convert into something like HTML." At that time, no one had the patience to sit and write while also adding formatting for how the text displayed on the web. Yet now, people are "stuffing" or creating variations based on markdown to add so many new formats that… they can’t even remember all the syntax.

    React is also an example. Since the time of PHP, there has been a desire to create something that clearly separates the user interface from the core logic processing of applications into two distinct parts for better readability and writing. The result is that UI/UX libraries have developed very robustly, providing excellent user interaction, while the application logic resides on a separate server. The duo of Front-end and Back-end emerged from this, with the indispensable REST API waiter. Yet now, React doesn’t look much different from PHP, leading to Vue, Svelte... all converging back to a single point.

    However, the loop is not bad; on the contrary, this loop is more about evolution than "regression." Sometimes, it creates something good from something old, and people rely on that goodness to continue the loop. In other words, it’s about distilling the essence little by little 😁

    » Read more
  • Alongside the official projects, I occasionally see "side" projects aimed at optimizing or improving the language in some aspects. For example, nature-lang/nature is a project focused on enhancing Go, introducing some changes to make using Go more user-friendly.

    Looking back, it resembles JavaScript quite a bit 😆

    » Read more

Issue

Complex calculations can often cause the Event Loop to become blocked, resulting in unresponsiveness of the server until the computation is complete.

Does this mean Node.js struggles with such calculations? In today's article, I will present two techniques to address this issue.

Partitioning

Also known as "chunking," this technique is based on the principle of dividing the calculation into smaller parts to ensure the interleaving of the Event Loop.

For example, consider an average calculation of numbers from 1 to n with a complexity of O(n):

for (let i = 0; i < n; i++)
  sum += i;
let avg = sum / n;
console.log('avg: ' + avg);

The complexity and CPU usage increases as n increases. However, by applying the Partitioning technique, the calculation can be divided into n asynchronous steps with a constant performance cost of O(1):

function asyncAvg(n, avgCB) {
  var sum = 0;
  function help(i, cb) {
    sum += i;
    if (i == n) {
      cb(sum);
      return;
    }

    setImmediate(help.bind(null, i+1, cb));
  }

  help(1, function(sum){
      var avg = sum/n;
      avgCB(avg);
  });
}

asyncAvg(n, function(avg){
  console.log('avg: ' + avg);
});

Partitioning utilizes setImmediate to perform each step of the loop asynchronously, ensuring that the Event Loop is not blocked since setImmediate is executed during the "Check" phase. To learn more, I recommend reading the article Understanding the Event Loop in Node.js (in Vietnamese). In simple terms, calculations are performed on each iteration of the Event Loop.

Offloading

If your task is more complex, Partitioning might not be sufficient. The Event Loop is responsible for ordering the execution of tasks, not directly executing them. To take advantage of multi-core processors, offload the tasks from the Event Loop.

There are two ways to apply Offloading:

To learn more about Worker Thread implementations, you can refer to the article What are Worker threads? Do you know when to use Worker threads in node.js? (in Vietnamese).

Conclusion

For simple tasks, such as iterating over elements of an arbitrarily long array, Partitioning can be a good choice. If your computational logic is more complex, Offloading is a better approach.

The Offloading technique incurs some overhead due to the communication between the serialized objects of the Event Loop and the Worker Pool. However, it can utilize the CPU's multiple cores.

However, if your server primarily performs heavy CPU computations, you should consider whether Node.js is the best fit. Node.js excels with asynchronous I/O tasks but may not be the optimal choice for CPU-intensive tasks.

References:

Premium
Hello

The secret stack of Blog

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (1)

Leave a comment...
Avatar
Gin Na2 years ago

Phép tính đc thực hiện trên mỗi lần lặp là sao ạ?

Reply
Avatar
Xuân Hoài Tống2 years ago

Bạn đọc lại bài viết https://2coffee.dev/bai-viet/tim-hieu-ve-vong-lap-su-kien-event-loop-trong-nodejs-8 này để biết thêm các event loop hoạt động nhé

Avatar
Gin Na2 years ago

@gif [3o6Zt6KHxJTbXCnSvu] Cảm ơn ad