Skip to main content

Implement Multi Threading in Nodejs with a real world use-case

In this article, we will see a problem statement and how you can solve that using nodejs multi threading concept.Implement Multi Threading in Nodejs with a real world use-case.

As we all know, Nodejs is a single threaded run time environment. If you are completely new to Node single thread concept. read this article to get a better understanding of it.

Since, Nodejs is single thread. Main constraint that Nodejs developers faced is running CPU intensive tasks in the Node applications.

But Node.js v11 came up with one the important features. that is, worker threads in Nodejs.

What are Worker Threads

worker threads in the Nodejs solves the problem of multi threading. there are some scenarios where you might need to run the task in different thread rather than running it in main thread.

In that case, worker threads takes the task without blocking the main thread of the application.

If you want to know more about the basic implementation of worker threads, refer this article to get better understanding of basics.

worker threads

Implementation - Problem Statement

Let's see where we might need worker threads in Nodejs with an use-case. Let's say that you are building an web application which has lot of stock videos.

When user uploads the video, you have to add the watermark of your application logo for copyright issues.

let's see how you can implement this scenario using worker threads.

Adding a watermark to video can be implemented using ffmpeg. but the problem in using that in nodejs application is, it is cpu intensive.

If you directly implement that in your application. it will block the main thread and affects the performance.

Solution

So, let's implement that using worker threads.

As always, create a directory and initialize the project using npm init --yes

Install the dependencies for the project.

1npm i express body-parser ffmpeg hbs multer
  • express - Nodejs Framework to handle the request and response
  • body-parser - it handles the request post body.
  • ffmpeg - This library is used to handle adding watermark to the uploaded video.
  • hbs - handlebars is a template engine to render the views in express applications.
  • multer - it is used to handle the file upload in nodejs applications.

Here, we are going to use babel to use ES6 in node application. babelbasically compiles the ES6 to javascript.

1npm i --save-dev @babel/cli @babel/core @babel/node @babel/preset-env dotenv nodemon

create a file called .babelrc and add the following code

1{
2 "presets": [
3 [
4 "@babel/preset-env",
5 {
6 "targets": {
7 "node": true
8 }
9 }
10 ]
11 ]
12}

After that, create a directory viewsand inside that directory, create a file index.hbs and add the following code,

1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <meta http-equiv="X-UA-Compatible" content="ie=edge" />
7 <title>Video Watermark</title>
8 <link
9 rel="stylesheet"
10 href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"
11 />
12 </head>
13
14 <body>
15 <h1>Watermarkify</h1>
16
17 {{# if error }}
18 <div class="ui negative message">
19 <i class="close icon"></i>
20 <div class="header">
21 {{error}}
22 </div>
23 </div>
24 {{/if}}
25
26 <form
27 method="POST"
28 class="ui form"
29 enctype="multipart/form-data"
30 action="/upload-video"
31 >
32 <div class="field">
33 <label>Upload Video</label>
34 <input type="file" name="ssvideo" accept="video/*" />
35 </div>
36 <button class="ui button">upload</button>
37 </form>
38 </body>
39</html>

basically, we have an html form which takes video file as an input.

Once, form is submitted, we need to handle the route to get the data in express.

1import express from "express"
2import * as bodyParser from "body-parser"
3import * as path from "path"
4import multer from "multer"
5
6require("dotenv").config()
7
8const storage = multer.diskStorage({
9 destination: "./uploads/",
10 filename: function(req, file, cb) {
11 cb(
12 null,
13 file.fieldname + "-" + Date.now() + path.extname(file.originalname)
14 )
15 },
16})
17
18const upload = multer({ dest: "uploads", storage: storage })
19
20const app = express()
21
22app.use(bodyParser.json())
23app.use(bodyParser.urlencoded({ extended: false }))
24
25app.set("views", path.join(__dirname, "views"))
26app.set("view engine", "hbs")
27
28app.get("/", (req, res) => {
29 res.render("index")
30})
31
32app.post("/upload-video", upload.single("ssvideo"), (req, res) => {
33 console.log(req.file)
34})
35
36const PORT = process.env.PORT
37
38app.listen(PORT, () => {
39 console.log(`Server is running on PORT ${PORT}`)
40})

Here, we setup the handlebar template engine along with multer in express application.

After that, we have two routes which handles the default index page and upload video url.

we will get the uploaded file data in /upload-video url, you can process the video in the route.

Now, it is time to implement the worker threads.

Worker Threads - Concepts

There are three main concepts in worker threads. they are WorkerisMainThread and workerData.

Mainly, Worker is used to create a worker thread and isMainThread check if the application is running on main thread and worker data is used to pass the data from main thread to worker thread.

In /upload-video route, check if it is running in main thread, if it is, create a worker thread and pass the uploaded file data to worker thread.

1app.post("/upload-video", upload.single("ssvideo"), (req, res) => {
2 if (isMainThread) {
3 let thread = new Worker("./threads/threaderone.js", {
4 workerData: {
5 file: req.file.path,
6 filename: req.file.filename,
7 watermark_image_url: image_url,
8 },
9 })
10
11 thread.on("message", data => {
12 res.download(data.file, req.file.filename)
13 })
14
15 thread.on("error", err => {
16 console.error("thread", err)
17 })
18
19 thread.on("exit", code => {
20 if (code != 0) console.error(`Worker stopped with exit code ${code}`)
21 })
22 }
23})

Here, we have three callback functions, they are message,error and exit. to get values from different events.

Creating Worker Threads

create a file called threaderone.js inside directory thread and add the following code.

1let ffmpeg = require("ffmpeg")
2const fs = require("fs")
3const { workerData, parentPort } = require("worker_threads")
4
5let dest = "/dest/video.mp4"
6
7try {
8 let process = new ffmpeg(workerData.file)
9
10 process.then(video => {
11 video.fnAddWatermark(
12 __dirname + "/watermark.png",
13 __dirname + "/" + workerData.filename,
14 {
15 position: "C",
16 },
17 function(err, file) {
18 if (!err) {
19 console.log("New video file is" + file)
20
21 parentPort.postMessage({ status: "Done", file: file })
22 }
23 }
24 )
25 })
26} catch (e) {
27 console.log(e.code)
28 console.log(e.msg)
29}

Basically, it gets the workerData from the main thread and do all the process of embedding watermark to the video and post the message to main thread.

Main thread gets the modified video and send it as response to the user.

In this process, main thread doesn't get blocked by the CPU intensive task.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...