主要用到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