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(${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 to complete, available: 44611 ms 👏 👏 👏 👏 👏 👏 👏 👏 👏 👏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

using
clusterModule,
Master-WorkerPattern to test


There are 3 JS, respectively, the main thread code:
master.js, child process code:
worker.js, 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; SetupMaster ({exec: "./worker.js", slient: true}); Const startTime = date.now (); function run() {const startTime = date.now (); // const totalCount = 500; // 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(); Worker. on("message", function(MSG) {// complete a count 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, */ function nextTask(worker) {const data = fbgenerator.next (); If (data.done) {done(); if (data.done) {done(); return; } worker.send(data.value);} worker.send(data.value); */ function done() {if (completedCount >= totalCount) {cluster.disconnect(); / / Function done() {completedCount >= totalCount) {cluster.disconnect(); Console. log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏"); Console. info(${date.now () - startTime}ms'); Console. log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏"); Function generator (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); Process. on("message", n => {var res = Fibonacci (n); Process. send(res); });Copy the code

Entry code:cluster.js

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 to complete, available: 10724 ms 👏 👏 👏 👏 👏 👏 👏 👏 👏 👏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.