DEV Community

Akash Shyam
Akash Shyam

Posted on

Use Multiple Threads in Node like a PRO

Time consuming and software intensive programs cannot be run on the main thread. With Piscina, create new threads with absolute ease.

Why Should You Care?

While building APIs, most of us have hashed passwords before storing it in our database(if you haven't... please do it). We often tend to go lightly on our hashing so that it does not affect performance. With multiple threads, this can be done separately without blocking the main thread and leaving the user hanging.

Example

Let's setup an NPM project:

npm init -y
Enter fullscreen mode Exit fullscreen mode

Install piscina:

npm i piscina
Enter fullscreen mode Exit fullscreen mode

Create an index.js file and add the following code:

const path = require('path');
const Piscina = require('piscina');

const piscina = new Piscina({
  filename: path.resolve(__dirname, 'worker.js')
});

(async function() {
  const result = await piscina.runTask({ a: 4, b: 6 });
  console.log(result);  // Prints 10
})();
Enter fullscreen mode Exit fullscreen mode

We've created a new piscina worker and passed in the absolute path to the worker. Then, in an async function, we've allotted a task to our worker.

Now, for our worker.... let's create a worker.js file:

module.exports = ({ a, b }) => {
  return a * b;
};
Enter fullscreen mode Exit fullscreen mode

Our workers can also be a promise.

const { promisify } = require('util');
// Make setTimeout() a promise
const timer = promisify(setTimeout);

module.exports = async ({ a, b }) => {
  // Fake some async code
  await timer(() => {
    console.log('1 second later');
  } ,1000);

  return a * b;
};
Enter fullscreen mode Exit fullscreen mode

What we've seen is pretty straightforward and not very difficult to implement on our own.... here's where piscina start's to shine:

  • Efficient Communication Between threads
  • Task Cancellation
  • Delaying Availability
  • Custom Task Queues
  • Statics for run and wait times
  • Async Tracking
  • Support for Typescript(yay!), common JS and ESM

We'll look at most of this throughout the post.

Cancellation

For this, we'll need use the events package.... this comes by default so no need to install anything. The event package gives us an EventEmitter which we can use to send events. Our worker listens to the abort event, stops executing and throws an error to let us know that it was successfully cancelled.

const path = require('path');
const Piscina = require('piscina');
const EventEmitter = require('events');

const piscina = new Piscina({
    filename: path.resolve(__dirname, './worker.js'),
});

(async function () {
    const eventEmitter = new EventEmitter();

    try {
        const task = piscina.runTask({ a: 4, b: 5 });

        eventEmitter.emit('abort');
        await task;
    } catch (error) {
        console.log('Task cancelled');
    }
})();
Enter fullscreen mode Exit fullscreen mode

If you look carefully, you will find that we haven't immediately awaited our task. If we don't do this, then by the time the abort event is emitted, the task would have finished executing.

This is by no means a complete list of what piscina can do.... checkout the official docs here.

That's it for now, I hope you guys liked this post. If you did please like it and follow me. Bye 👋

Top comments (2)

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt

Why this library, not native? What about alternative libraries?

Collapse
 
akashshyam profile image
Akash Shyam

I mentioned some of the functionality that is easier to implement using this library compared to native. I used this and I liked this..... there may be better libraries out there. This is a good option in my opinion.