开始使用Node.js工作线程
众所周知,Node.js是单线程的,允许在给定时间内执行单个命令。例如,执行处理器密集型的服务器端代码可能会阻塞事件循环,减缓其他后续请求的执行。
简介
为了解决这个问题,Node.js v10.5中引入了工作线程模块。
在本教程中,我们将了解工人线程的概念,它是如何工作的,以及它将如何帮助我们执行CPU密集型任务而不阻塞其他请求。
前提条件
- 在你的开发环境中安装Node.js
- JavaScript同步和异步编程的基础知识。
- 对Node.js的工作原理有充分的了解。
学习目标
在本期Node.js工作线程课程结束时,你应该能够。
- 理解Node.js工作线程的工作原理。
- 使用工人线程。
- 充分发挥工人线程的作用。
- 理解工人线程池的概念。
探索Node.js中的工人线程API
Node.js自带worker_threads 模块。这个模块有助于并行地运行JavaScript代码。
工作者线程负责通过传输ArrayBuffer 实例来处理CPU密集型的任务。
由于以下特点,它们已被证明是CPU性能的最佳解决方案。
- 它们用多个线程运行一个进程。
- 每个线程执行一个事件循环。
- 每个线程运行单个JS引擎实例。
- 每个线程执行单个Node.js实例。
工人线程如何工作
工人线程通过执行主线程指定的一段代码来工作。每个工作者都在与其他工作者隔离的情况下执行。但是,这些工作者可以根据需要通过消息通道来回传递消息。
父工作者使用worker.postMessage() 将消息写入通道,而子工作者使用parentPort.postMessage() 函数。
由于JavaScript不支持并发,Node.js工作者利用V8,允许工作者与其他现有工作者完全隔离地运行。
使用工作者线程
在本节中,让我们创建一个工作者线程的例子,并将其传递给一些假数据。
//add this code snippet to main.js
const { Worker } = require('worker_threads')
const runService = (WorkerData) => {
return new Promise((resolve, reject) => {
// import workerExample.js script..
const worker = new Worker('./workerExample.js', { WorkerData });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`stopped with ${code} exit code`));
})
})
}
const run = async () => {
const result = await runService('hello John Doe')
console.log(result);
}
run().catch(err => console.error(err))
// add this to workerExample.js file.
const { WorkerData, parentPort } = require('worker_threads')
parentPort.postMessage({ welcome: WorkerData })
输出
{ welcome: 'hello John Doe' }
在我们的main.js 脚本中,我们首先从worker_threads 导入Worker ,然后传递data(filename) 供工作者处理。
下一步是监听来自工人线程的消息事件,正如在workerExample.js 服务中看到的那样。
这个工人服务拥有从我们的主应用程序发送的WorkerData ,以及通过parentPort 发送回已处理数据的方法。这个对象(parentPort )有postMessage() ,我们用它来传递处理过的数据。
工作者线程也提供了共享内存的方法,使用我们之前看到的
SharedArrayBuffer instances。记住共享内存也可以通过传输ArrayBuffer instances。
创建和执行新的工作者
在这一节中,让我们看看CPU密集型的例子,生成一个斐波那契数列。
这个任务,如果在没有工作者线程的情况下生成,随着nth ,主线程会被阻塞。
//
In your `index.js` file, add the following:
const {Worker} = require("worker_threads");
let number = 10;
const worker = new Worker("./myWorker.js", {workerData: {num: number}});
worker.once("message", result => {
console.log(`${number}th Fibonacci No: ${result}`);
});
worker.on("error", error => {
console.log(error);
});
worker.on("exit", exitCode => {
console.log(`It exited with code ${exitCode}`);
})
console.log("Execution in main thread");
//add this script in myWorker.js file
const {parentPort, workerData} = require("worker_threads");
parentPort.postMessage(getFibonacciNumber(workerData.num))
function getFibonacciNumber(num) {
if (num === 0) {
return 0;
}
else if (num === 1) {
return 1;
}
else {
return getFibonacciNumber(num - 1) + getFibonacciNumber(num - 2);
}
}
输出
Execution in the main thread
10th Fibonacci No: 55
It exited with code 0
在index.js 文件中,我们从Worker 类的一个实例中创建了一个工作线程,正如我们在前面的例子中看到的那样。
为了获得结果,我们监听3个事件。
message当工作者发布消息时执行的事件。exit在工作者停止执行时被触发的事件。error是在发生错误时触发的。
我们在index.js 中的最后一行。
console.log("Execution in main thread");
这是在主线程中的执行,在我们等待worker的结果时,将执行该执行,如上面的输出所见。
因此,只要在工作者线程中处理一个CPU密集型任务,我们就可以处理任何其他任务,而不必担心阻塞主线程。
总结
由于Node.ja在处理CPU密集型任务时的性能问题,它一直被人诟病。工作线程的引入有效地解决了这些缺点,从而提高了Node.js的能力。