This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
Worker Threads
Multithreading in Node is a new feature introduced in Node VERSION V10.5.0. Worker Thread has been an experimental feature for a long time. So far, the stable version of Node has reached 14.17.4, and this feature has become stable and available.
What is worker_thread
Let’s start with the description on the website
The `worker_threads` module enables the use of threads that
execute JavaScript in parallel.
Copy the code
The worker_Thread module allows JavaScript to be executed in parallel using threads.
We used to have Javascript that was single threaded and then we had an event loop that was constantly listening for functions coming in from the execution stack. For worker_Thread, it can be understood that there are multiple javascript worker threads in an event loop. Creating a thread is equivalent to creating a new JS execution environment. Multithreading in action is shown below
And child_process
Child_process can create a new node process. The difference between worker_thread and child_process is that worker_thread can share memory, and common data can be shared between threads. Child_process can only pass data through JSON.
Also, since threads are within a process, creating a thread is less expensive than creating a process
Worker_thread Range
Worker_thread is useful for CPU-intensive JS operations, but it doesn’t provide much performance improvement for IO intensive operations. Instead, Node’s built-in one-step IO operations are more useful than worker threads.
Using worker_thread
Having introduced the concept of worker_thread, it’s time to introduce its use
Creating a worker thread
const { isMainThread, Worker, parentPort } = require('worker_threads');
if (isMainThread) {
createChildThread();
} else {
parentPort.on('message'.(msg) = > {
console.log('parent thread listen:', msg);
})
parentPort.postMessage('hello child thread')}// Create the thread method
function createChildThread() {
const worker = new Worker(__filename)
worker.on('message'.(val) = > {
console.log('child thread listen:', val);
worker.postMessage('hello parent thread'); })}Copy the code
The above code calls the method of creating a worker thread when the main thread is created, creates a worker thread and adds another one. After the worker thread is created, it calls the port of the main thread to send a message to the worker thread, and the worker thread receives the message and responds to the main thread. The general flow chart is as follows
Return after execution
Before creating a thread
After a thread is created, the number of threads is +1
Interthread communication
As mentioned above, worker_Threads can be shared through ArrayBuffer or SharedArrayBuffer. Let’s take a look at how this is implemented in code.
const { isMainThread, Worker, parentPort } = require('worker_threads');
if (isMainThread) {
createChildThread();
} else {
// Create a SharedArrayBuffer of 4 bytes
const shareBuf = new SharedArrayBuffer(4);
// Create an array of 8-bit unsigned integers
const bufInt = new Uint8Array(shareBuf);
parentPort.on('message'.() = > {
console.log('parent thread listen:', bufInt);
});
// Send the shared memory created integer
parentPort.postMessage({ bufInt });
}
// Create the thread method
function createChildThread() {
const worker = new Worker(__filename);
worker.on('message'.({ bufInt }) = > {
console.log('child thread listen:', bufInt);
bufInt[0] = 11;
worker.postMessage('finished');
});
}
Copy the code
When you return from running, you can see that you created one in the child threadSharedArrayBuffer
It is a shared Buffer between threads, so the main thread can also see the data modified in the child thread.
As shown in the figure, useSharedArrayBuffer
The created value is allocated to shared memory, which can be shared by all threads.
Interthread communication
We have learned to create a thread and the use of Shared memory, you can see from the above code, the thread is sending a message from the main thread, and then the child thread to the main thread to reply message, there is no way to let two child threads directly communication, if I want two child thread communication directly, that need to use MessageChannel this class, The detailed use of MessageChannel can be found here. Now let’s implement direct communication between threads at once.
const {
isMainThread,
Worker,
parentPort,
MessageChannel,
threadId,
} = require('worker_threads');
if (isMainThread) {
createChildThread();
} else {
parentPort.on('message'.({ port }) = > {
// Configure the communication port method after the main thread receives the port
port.on('message'.(msg) = > {
console.log(`port${threadId} listen:`, msg);
});
port.postMessage(`hello, im thread ${threadId}`);
});
parentPort.postMessage('hello');
}
// Create the thread method
function createChildThread() {
const { port1, port2 } = new MessageChannel(); // Create a MessageChannel
const worker1 = new Worker(__filename); // Create child thread 1
const worker2 = new Worker(__filename); // Create child thread 2
worker2.postMessage({ port: port1 }, [port1]); // Port 1 that sends the Channel to the main thread
worker1.postMessage({ port: port2 }, [port2]); // Port 1 that sends the Channel to the main thread
}
Copy the code
Run the code and return
As you can see from the code implementation, the final step to establish direct communication between the child threads is in the message event of the main thread. The flowchart for establishing communication is as follows
In actual combat
If you have a million pieces of data in an array that requires MD5 encryption, compare the speed of the implementation with or without a worker thread
const {
isMainThread,
Worker,
parentPort,
threadId,
} = require('worker_threads');
const { createHash } = require('crypto');
const ARR_NUM = 1000000; // Array length
const WORKER_NUM = 1; / / the number of threads
const size = Math.ceil(ARR_NUM / WORKER_NUM); // The amount of data that each thread needs to process
if (isMainThread) {
createChildThread();
} else {
parentPort.on('message'.({ status, index, startTime }) = > {
if (index === WORKER_NUM) {
const usedTime = Date.now() - startTime;
console.log(`finish bussiness time: ${usedTime}ms`); process.exit(threadId); }});const data = addHasCode(threadId, size, (threadId - 1) * size);
/ / is completed
parentPort.postMessage({
business: 'finish work',
data,
});
}
// Create the thread method
function createChildThread() {
let finishNumBuf = new SharedArrayBuffer(4);
let finishNum = new Uint8Array(finishNumBuf);
const startTime = Date.now();
for (let x = 0; x < WORKER_NUM; x++) {
const worker = new Worker(__filename, {});
worker.on('message'.({ business, data }) = > {
if (business === 'finish work') {
finishNum[0] + +; worker.postMessage({status: `finish worker ${x}`.index: finishNum[0].startTime: startTime, data, }); }}); }console.log(`${WORKER_NUM} thread start working`);
}
// Encryption method
function addHasCode(index, size, limit) {
const result = [];
for (let x = limit, num = index * size; x < num; x++) {
result.push(createHash('md5').update('hello world').digest('base64'));
}
return result;
}
Copy the code
use1
The average number of threads is calculated2700ms
Or so
use5
The average number of threads is calculated2000ms
Or so
use20
The average number of threads is calculated2500ms
Or so
use60
The average number of threads is calculated3800ms
Or so
It can be seen that more threads is not the better, too many threads may increase too much system overhead, speed is not as good as the single thread time.
summary
This article introduced the concept of worker_Threads in nodeJs, the difference between multiple processes, and its benefits. How worker_Threads works, shared memory, and communication between child threads. Finally, we use an example to test the efficiency of a child process to show worker_Threads versus a single thread.
If there are any inaccuracies or errors in this article, please note them in the comments section
reference
- Worker Theread