Node.js is single-threaded versus multi-process

Node.js is known for high performance and is widely used for asynchronous event-driven, non-blocking I/O. However, its disadvantages are also obvious. Because Node.js is a single-threaded program, if it takes a long time to run, the CPU will not be released in time, so it is not suitable for CPU-intensive applications.

Of course, there are ways to solve this problem. Although Node.js does not support multithreading, it is possible to create multiple child processes to perform tasks. Node.js provides two modules child_process and cluster for creating multiple child processes

Let’s simulate finding a large number of Fibonacci numbers for cpu-intensive tests using single thread and multiple processes respectively

The following code is to find Fibonacci number 35 500 times.

Single threaded processing

Code:single.js

function fibonacci(n) {
  if (n == 0 || n == 1) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2); }}let startTime = Date.now();
let totalCount = 500;
let completedCount = 0;
let n = 35;

for (let i = 0; i < totalCount; i++) {
  fibonacci(n);
  completedCount++;
  console.log(`process: ${completedCount}/${totalCount}`);
}
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
console.info('Task completed, time:The ${Date.now() - startTime}ms`);
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
Copy the code

Execute node single.js to see the result

On my computer, the result is 44611ms(it varies depending on your computer configuration).

. process:500/500👏 👏 👏 👏 👏 👏 👏 👏 👏 👏 Task completed when:44611Ms 👏 👏 👏 👏 👏 👏 👏 👏 👏 👏Copy the code

500 searches takes 44 seconds, which is too slow. Imagine if there were more places, more numbers…

Let’s try using multiple processes at ⬇️

Multiple processes

Cluster module and master-worker mode are used to test a total of 3 JS, with Master thread code: master.js, child process code: worker.js and entry code: Cluster.js (there is no need to write a separate JS entry for clarity)

Main thread code:master.js

const cluster = require("cluster");
const numCPUs = require("os").cpus().length;

// Set the child to execute the program
cluster.setupMaster({
  exec: "./worker.js".slient: true
});

function run() {
  // Record the start time
  const startTime = Date.now();
  / / the total number of
  const totalCount = 500;
  // Number of processed tasks
  let completedCount = 0;
  // Task generator
  const fbGenerator = FbGenerator(totalCount);

  if (cluster.isMaster) {
    cluster.on("fork".function(worker) {
      console.log(`[master] : fork worker ${worker.id}`);
    });
    cluster.on("exit".function(worker, code, signal) {
      console.log(`[master] : worker ${worker.id} died`);
    });

    for (let i = 0; i < numCPUs; i++) {
      const worker = cluster.fork();

      // Receive child process data
      worker.on("message".function(msg) {
        // Complete one, record and print the progress
        completedCount++;
        console.log(`process: ${completedCount}/${totalCount}`);

        nextTask(this); }); nextTask(worker); }}else {
    process.on("message".function(msg) {
      console.log(msg);
    });
  }

  /** * continue with the next task ** @param {ChildProcess} worker ChildProcess object, on which this task will be executed */
  function nextTask(worker) {
    // Get the next argument
    const data = fbGenerator.next();
    // Determine whether the program is completed. If so, call the completion function to end the program
    if (data.done) {
      done();
      return;
    }
    // Otherwise continue the task
    // Send data to the child process
    worker.send(data.value);
  }

  /** * done, call this function to end the program */ when all tasks are complete
  function done() {
    if (completedCount >= totalCount) {
      cluster.disconnect();
      console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
      console.info('Task completed, time:The ${Date.now() - startTime}ms`);
      console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏"); }}}/** * generator */
function* FbGenerator(count) {
  var n = 35;
  for (var i = 0; i < count; i++) {
    yield n;
  }
  return;
}

module.exports = {
  run
};
Copy the code

1. The child process is created based on the number of logical CPU cores on the current computer. The number of logical CPU cores on the current computer will vary depending on the number of logical CPU cores

2. The main thread communicates with the sub-process through send to send data, and listens for message events to receive data

3. I don’t know if you’ve noticed that I’m using the ES6 Generator here to simulate the Fibonacci position I need to find each time (although it’s dead 😂 to ensure consistency with the single thread above). The reason for this is that you don’t want to throw all the tasks out at once, because even if you throw them out, they will block, so you can control them on the application side, complete one, and drop another.

Child process code:worker.js

function fibonacci(n) {
  if (n == 0 || n == 1) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2); }}// Receive the task sent by the main thread and start to find the Fibonacci number
process.on("message", n => {
  var res = fibonacci(n);
  // Notify the main thread when the search is complete, so that the main thread can assign the task again
  process.send(res);
});
Copy the code

Entry code:cluster.js

// Import main thread js and execute the exposed run method
const master = require("./master");
master.run();
Copy the code

Run the node cluster.js command to view the result

On my computer, the result was 10,724ms (it varies depending on your computer configuration).

process: 500/500👏 👏 👏 👏 👏 👏 👏 👏 👏 👏 Task completed when:10724Ms 👏 👏 👏 👏 👏 👏 👏 👏 👏 👏Copy the code

The results of

After comparing the two methods, it is clear that multi-process processing is more than 4 times faster than single-thread processing. And if there are conditions, the computer will be faster if it has enough CPU and more processes.

If there’s a better solution or another language that can handle your needs all the better, Node.js isn’t naturally suited for CPU-intensive applications.

Read the original