Worker threads in NodeJS
Threads in NodeJS are here since v10.5.0. But wait... How did Node work before with no workers threads? Read on to get to know more!
What can we do to handle CPU intensive tasks in NodeJS?
Let’s see an example app to see worker_threads in action. We’re gonna look at two examples. In the first one we’ll see how we can execute CPU intensive operations in three threads and how they can communicate with the main thread.
Open up the two files in your favourite editor. We’re going to create three threads that are going to race with each other. At the end we’re gonna see which one is the winner. They’ll also report back their status so that we can see where they’re at. So let’s see the code.
Let’s see what I did here. I imported the path and the worker threads modules. I created the threads using the Worker class from the imported module. As you can see in the constructor we have to pass the file which we want to be executed on a different thread. In the second argument we can pass options like the workerData. It is used to send some initial data to the worker. The HTML structured cloning algorithm is used to clone the data. I then register a couple of events (Note that the Worker class extends the EventEmitter) to be able to receive some information from the threads. online is called when the thread starts, exit when finishes and I think the other two are quite obvious. Let’s take a look at the job.js file.
I import quite a few things here. workerData is the passed data in the worker’s constructor so we can use it to access that data passed by the main thread. threadId is the id of the current thread. If a spawned thread is running the code where the parentNode is imported, then it can be used to send messages to the parent thread. It is actually a MessagePort object which was created under the hood by Node. MessagePort is an asynchronous, two-way data channel. In the for I go from 1 to the passed limit and at every 10% I send a message to the parent thread about the progress. Since this is only an experimental feature, we have to use a special flag when executing. Of course you should have at least v10.5.0 installed to use this module as it is introduced from that version. Run this command.
This is an example outcome and of course yours might be different. But we can see some racing here. Also, you can see that in the main thread we can access the messages and events of the workers. Pretty cool! Let’s add some new features. Let’s say we want to declare a winner and send back the position of each thread when they finish. So I want to show you how to send data from the main thread to the spawned threads.
Add this line in the index.js after the second line.
And modify the online event’s listener.
In the job.js add this before the for.
Note that if you use on instead of once the thread won’t exit because it has an active listener which cannot be garbage collected. Run again the app.
You can see that the message from the main thread arrived to the spawned threads. In this example we’ve looked at how you can create new threads as well as how to communicate between the threads.
In the next example we’re gonna look at how we can share data between the threads. Since these threads are in the same process it is possible to share data should you wish to. Create a file for the example.
Copy this code in the index2.js.
Now both the main and the spawned thread will execute the same file. With the isMainThread flag we can decide if the current thread is the main one. If so we create a worker and create a SharedArrayBuffer object which is a binary data buffer. We create a Uint8Array object which is a one byte unsigned integer and we’ll pass the SharedArrayBuffer object to its constructor so that it will use the buffer’s space in memory. Then I set its value to a one element array containing the number 1. I register a callback for the messages then I pass the shared object to the spawned thread. In the else branch I first post a message to the main thread to check the current content of the shared object. So the main thread prints it. As you can see I update the shared object in a setTimeout block because if I didn’t then when the main thread wants to print the shared object the spawned thread has already updated it so basically the first console.log would print the updated shared object too. You can run the app with this command.
You can see the second line contains the updated version of the shared object. It was logged in the main thread but was modified in the spawned thread so we can see the memory is shared.
We’ve seen some part of the worker_threads API. This has not been everything so if your interested further go to the official website. Since this is still in experimental stage I wouldn’t recommend using it in production but let’s hope it’ll become a stable part of NodeJS soon. A final note about the creation of threads. As we use a pool for database connections, we should use a pool of threads instead of always creating new ones so that the overhead of the creation can be avoided.
You can find the code on my GitHub. I hope you enjoyed it and learned from my article something. If so, click on the recommend button below and share my article. Cheers!
On 9th September 2019, the worker_threads module was marked stable. So the API is not expected to receive any breaking changes.