Express.js Worker Threads
Worker Threads in Node.js allow you to run JavaScript in parallel threads, enabling you to take advantage of multi-core systems and perform CPU-intensive tasks without blocking the main thread. This guide covers key concepts, examples, and best practices for using Worker Threads in Express.js applications.
Key Concepts of Worker Threads
- Worker: A class that represents an independent JavaScript execution thread.
- parentPort: The communication channel between the main thread and the worker thread.
- workerData: Data passed from the main thread to the worker thread when it is created.
Creating a Worker Thread
Use the Worker class to create a new worker thread that can run a separate JavaScript file:
Example: Creating a Worker Thread
// worker.js
const { parentPort, workerData } = require('worker_threads');
parentPort.on('message', (msg) => {
console.log(`Worker received: ${msg}`);
parentPort.postMessage(`Worker says: ${msg}`);
});
// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;
app.get('/start-worker', (req, res) => {
const worker = new Worker('./worker.js', { workerData: null });
worker.postMessage('Hello, worker thread');
worker.on('message', (msg) => {
res.send(`Worker thread replied: ${msg}`);
});
worker.on('error', (err) => {
res.status(500).send(`Worker error: ${err.message}`);
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Using workerData
Pass data from the main thread to the worker thread using the workerData
option:
Example: Using workerData
// worker.js
const { parentPort, workerData } = require('worker_threads');
parentPort.postMessage(`Received data: ${workerData}`);
// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;
app.get('/start-worker', (req, res) => {
const worker = new Worker('./worker.js', { workerData: 'Hello, worker thread' });
worker.on('message', (msg) => {
res.send(msg);
});
worker.on('error', (err) => {
res.status(500).send(`Worker error: ${err.message}`);
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Handling Worker Events
Handle various worker events such as online
, exit
, and error
:
Example: Handling Worker Events
// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;
app.get('/start-worker', (req, res) => {
const worker = new Worker('./worker.js', { workerData: 'Hello, worker thread' });
worker.on('online', () => {
console.log('Worker is online');
});
worker.on('message', (msg) => {
res.send(msg);
});
worker.on('error', (err) => {
res.status(500).send(`Worker error: ${err.message}`);
});
worker.on('exit', (code) => {
console.log(`Worker exited with code: ${code}`);
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Communicating with Worker Threads
Use the postMessage
method to send messages between the main thread and worker threads:
Example: Communicating with Worker Threads
// worker.js
const { parentPort } = require('worker_threads');
parentPort.on('message', (msg) => {
console.log(`Worker received: ${msg}`);
parentPort.postMessage(`Worker says: ${msg}`);
});
// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;
app.get('/start-worker', (req, res) => {
const worker = new Worker('./worker.js');
worker.postMessage('Hello, worker thread');
worker.on('message', (msg) => {
res.send(`Worker thread replied: ${msg}`);
});
worker.on('error', (err) => {
res.status(500).send(`Worker error: ${err.message}`);
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Using Multiple Worker Threads
Use multiple worker threads to handle concurrent tasks efficiently:
Example: Using Multiple Worker Threads
// worker.js
const { parentPort, workerData } = require('worker_threads');
parentPort.postMessage(`Worker ${workerData.id} received data: ${workerData.msg}`);
// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;
app.get('/start-workers', (req, res) => {
const results = [];
const createWorker = (id) => {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', { workerData: { id, msg: 'Hello, worker thread' } });
worker.on('message', (msg) => {
results.push(msg);
resolve();
});
worker.on('error', (err) => {
reject(err);
});
});
};
Promise.all([createWorker(1), createWorker(2), createWorker(3)])
.then(() => {
res.send(results);
})
.catch((err) => {
res.status(500).send(`Worker error: ${err.message}`);
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Best Practices for Using Worker Threads
- Limit Resource Usage: Ensure worker threads do not consume excessive resources.
- Handle Errors: Always handle errors in worker threads to prevent crashes.
- Use Proper Communication: Use the
postMessage
method for proper communication between the main thread and worker threads. - Clean Up Threads: Ensure worker threads are properly cleaned up after completion.
- Security: Ensure secure handling of data and commands in worker threads to prevent security risks.
Testing Worker Threads
Test your worker thread-based code using frameworks like Mocha, Chai, and Supertest:
Example: Testing Worker Threads
// Install Mocha, Chai, and Supertest
// npm install --save-dev mocha chai supertest
// test/worker-thread.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
app.get('/start-worker', (req, res) => {
const worker = new Worker('./worker.js', { workerData: 'Hello, worker thread' });
worker.on('message', (msg) => {
res.send(msg);
});
worker.on('error', (err) => {
res.status(500).send(`Worker error: ${err.message}`);
});
});
describe('GET /start-worker', () => {
it('should start the worker thread and receive a reply', (done) => {
request(app)
.get('/start-worker')
.expect(200)
.end((err, res) => {
if (err) return done(err);
expect(res.text).to.include('Received data:');
done();
});
});
});
// Define test script in package.json
// "scripts": {
// "test": "mocha"
// }
// Run tests with NPM
// npm run test
Key Points
- Worker: A class that represents an independent JavaScript execution thread.
- parentPort: The communication channel between the main thread and the worker thread.
- workerData: Data passed from the main thread to the worker thread when it is created.
- Follow best practices for using Worker Threads, such as limiting resource usage, handling errors, using proper communication, cleaning up threads, and ensuring security.
Conclusion
Worker Threads in Node.js allow you to run JavaScript in parallel threads, enabling you to take advantage of multi-core systems and perform CPU-intensive tasks without blocking the main thread. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively manage worker threads in your Express.js applications. Happy coding!