在了解工作线程的具体用法之前,有必要先想想:工作线程解决了什么问题?
工作线程主要解决的是cpu密集型场景下的问题,由于node只有单个主线程的特性,导致在执行高cpu运算任务时,会有以下的问题:
而工作线程通过开启在主线程中开启新的线程单独执行计算任务,避免了阻塞整个事件循环,使主线程仍然可以继续处理后续的请求。
并且由于是新的线程,可以在其他cpu核心上执行,使得单个进程可以更充分的利用多核cpu。
主线程中执行,生成工作线程
const {
Worker } = require('worker_threads');
let worker = new Worker('工作线程的js文件路径', {
});
worker.on('message', (val) => {
resolve(val); //接收工作线程计算完毕后返回的结果
});
工作线程中,计算结果,向主线程返回
const {
workerData, parentPort } = require('worker_threads');
// workerData 主线程传来的参数
//计算结果 res
parentPort.postMessage(res); //向主线程返回结果
其他用法见 node文档
下面是一个阻塞主线程以及使用工作线程优化的示例。示例代码
使用koa框架启动了一个简单的http服务,包含以下三个api
/test
:用于测试主线程是否被阻塞,正常情况应当立即返回
/fib
:用暴力方式计算斐波那契数,随着输入的n增大,运算量呈指数增长,用于测试阻塞主线程
/asyncFib
:将计算交由工作线程执行,测试工作线程的优化效果
其中,asyncFib用 Promise
包装了一下,使得它从事件监听的形式改成一个普通的异步调用
//app.js
const {
Worker } = require('worker_threads');
const Koa = require('koa');
const Router = require('koa-router')
const app = new Koa();
const router = new Router();
app.use(router.routes());
router.get('/test', async (ctx, next) => {
ctx.body = 'test';
});
router.get('/fib',async (ctx,next)=>{
let n = ctx.query.n;
ctx.body = fib(n);
})
router.get('/asyncFib',async (ctx,next)=>{
let n = ctx.query.n;
ctx.body = await asyncFib(n);
})
app.listen(3000);
console.log('http://127.0.0.1:3000');
function fib (n) {
if (n === 1 || n === 2) return 1;
return fib(n - 1) + fib(n - 2);
}
async function asyncFib (n) {
let worker = new Worker('./fib.js', {
workerData: n });
return new Promise((resolve) => {
worker.on('message', (val) => {
resolve(val); //接收工作线程计算完毕后返回的结果
});
});
}
// fib.js
const {
workerData, parentPort } = require('worker_threads');
let num = workerData;//获取参数
let res = fib(num);
parentPort.postMessage(res); //向主线程返回结果
function fib (n) {
if (n === 1 || n === 2) return 1;
return fib(n - 1) + fib(n - 2);
}
http://127.0.0.1:3000/test
, 立刻返回响应testhttp://127.0.0.1:3000/fib?n=44
,处于pending状态,在我的测试中约10秒后返回http://127.0.0.1:3000/test
,也处于peding状态,要等到/fib
请求结束后才能收到响应可以看到,请求2不仅自己执行的慢,还影响到了后续请求,令其他请求都要等待它执行完成后才能处理。
http://127.0.0.1:3000/test
, 立刻返回响应testhttp://127.0.0.1:3000/asyncFib?n=44
,处于pending状态,也是约10秒后返回http://127.0.0.1:3000/test
,立刻返回响应test请求2不再阻塞主线程
就表现来说,加上了工作线程,请求就突然不被阻塞了。要如何理解这个现象?
首先要明确一点,在优化前后斐波那契数的计算量是固定的,该做的计算并不会凭空消失。工作线程是通过在另一个cpu核心上运行单独线程进行计算,从而实现不阻塞主线程。
我们都知道node.js 可以非阻塞的执行io操作,可以将工作线程与它进行类比,便于理解。
举个栗子:假设有这么一个服务,它以http接口的形式提供了一个计算斐波那契数的api http://api.test.com/fib?n=40
,再由我们的服务去调用它,这个流程与工作线程执行流程的对照如下:
调用外部的计算服务 | 使用工作线程 |
---|---|
发起http请求,交给io线程 ,注册一个回调等待响应 request(url, (res)=>{}) |
生成工作线程 ,监听message事件等待响应 worker.on('message',(res)=>{}) |
继续响应其他请求 | 继续响应其他请求 |
远程的计算服务获取参数n,用远程服务器的cpu 计算斐波那契数 | 工作线程获取参数n,用本机的其他空闲cpu核心 计算斐波那契数 |
返回结果,执行回调 | 返回结果,执行回调 |
可以看出,除了不阻塞主线程这一个好处之外,工作线程给node.js提供了单进程情况下使用多核cpu的能力,可以承载更多的计算量。