【node】線程池的封裝和使用

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


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章