主要用到worker_threads
(1)以下是线程池的封装,可以直接拿来用
const path = require('path');
const { Worker } = require('worker_threads');
class WorkerPool {
//构造函数
constructor(workerPath, numOfThreads) {
//执行文件路径
this.workerPath = workerPath;
//线程个数
this.numOfThreads = numOfThreads;
//初始化数据
this.init();
}//初始化数据
init() {
//线程引用数组
this._workers = [];
//已经被激活的线程数组
this._activeWorkers = [];
//排队队列
this._queue = [];
//如果线程个数<1,抛出错误
if (this.numOfThreads < 1) {
throw new Error('线程池最小线程数应为1');
}
//for循环创建子线程
for (let i = 0;i < this.numOfThreads; i++) {
//创建子线程,执行耗时操作
const worker = new Worker(this.workerPath);
//将子线程加入线程数组
this._workers[i] = worker;
//新建的线程都处于非激活状态
this._activeWorkers[i] = false;
}
}// 结束线程池中所有线程
destroy() {
for (let i = 0; i < this.numOfThreads; i++) {
//如果依然存在活跃线程,抛出错误
if (this._activeWorkers[i]) {
throw new Error(`${i}号线程仍在工作中...`);
}
//如果此线程没有在工作,关闭线程
this._workers[i].terminate();
}
}// 检查是否有空闲worker
checkWorkers() {
//检查是否又闲置线程,并返回
for (let i = 0; i < this.numOfThreads; i++) {
if (!this._activeWorkers[i]) {
return i;
}
}
return -1;
}
//对外运行函数,接收外界传过来的参数
run(getData) {
//返回Promise,因为使用的时候await等待着Promise返回值
return new Promise((resolve, reject) => {
//查找闲置线程
const restWorkerId = this.checkWorkers();
//把数据和block包装好
const queueItem = {
getData,//传进来的数据
callback: (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
}//block的展开
}
// 如果线程池已满,那么就往队列中添加数据
if (restWorkerId === -1) {
this._queue.push(queueItem);
return null;
}
// 如果有空余线程,那就拿到空余线程id和操作包直接跑
this.runWorker(restWorkerId, queueItem);
})
}
//操作子线程
async runWorker(workerId, queueItem) {
//得到将要被操作的子线程
const worker = this._workers[workerId];
//将此线程激活,防止被其他任务使用
this._activeWorkers[workerId] = true;// 线程信息回调
const messageCallback = (result) => {
//调用block存值,然后run()中block展开,resolve拿到值,Promise传值成功
queueItem.callback(null, result);
cleanUp();
};
// 线程错误回调
const errorCallback = (error) => {
//调用block存值,然后run中block展开,reject拿到值,Promise传值成功
queueItem.callback(error);
cleanUp();
};// 任务结束消除旧监听器,若还有待完成任务,继续完成
const cleanUp = () => {
//去除对message和error的消息监听,是worker本身的方法
worker.removeAllListeners('message');
worker.removeAllListeners('error');
//将对应线程放回闲置状态
this._activeWorkers[workerId] = false;
//如果排队队列是空的,说明已经没有多余任务要执行了,我们结束就可以了
if (this._queue.length == 0) {
return null;
}
//如果排队队列中还有任务,那么这个线程也别让它闲着了,重新激活开始处理排队任务的操作包
this.runWorker(workerId, this._queue.shift());
//PS:shift()用于将数组的第一个元素从数组中删除,并返回第一个元素
}//=============此方法进来后直接执行的是这部分 ===================
// 线程创建监听结果/错误,并执行上边的回调方法
worker.once('message', messageCallback);
worker.once('error', errorCallback);
// 1.向子线程传递初始data
worker.postMessage(queueItem.getData);
}
}
(2)线程池的使用:02-cpu_worker.js是耗时操作
//然后新建和执行
const pool = new WorkerPool(path.join(__dirname, '02-cpu_worker.js'), 4);
//创建有是个元素的数组,并且都填充为null
const items = [...new Array(10)].fill(null);/*
Promise.all
参数:应该是一个promise组成的数组
返回值:当Promise.all数组中所有promise都reolve之后,在then中返回结果数组,如果抛出错误,只会返回报错的那一个
*/
Promise.all(
//在这里给items填充内容,如果在外边填充
//用map而不是forEach的原因是,map最终会返回一个新数组
items.map(
//async默认返回promise,会给上边新建的空数组填充,promise元素
async (item, i,list) => {
const res = await pool.run(i);
console.log(`任务${i}完成了:`,res);//单个输出了,那就没必要在下边all整个输出了
/*
//如果不返回res,则最终结果是undefined,
map()对每一个元素操作return,并返回一个新数组,需要有返回值。
*/
return res;//async返回的值会被Promise.resolve包装,所以满足Promise.all的需求
}
))
.then((result) => {
console.log(result);
// 销毁线程池
pool.destroy();})
.catch((err)=>{
console.log(err);
//销毁线程池
pool.destroy();
});
(3)跑起来
因为worker_threads是实验模块,所以跑起来是下边的方法:
node --experimental-worker 文件名.js
POST:
github:https://github.com/canwhite/QCNodeWorkerThreads
https://www.jb51.net/article/158538.htm
https://blog.csdn.net/zero_person_xianzi/article/details/99412641
https://juejin.im/post/5c63b5676fb9a049ac79a798