上一篇文章提到:我們需要合理的使用 Worker,盡量重用已有的 Worker 實例,避免不斷建立新的實例;此外,在不需要用到 Worder 時,使用 terminate
方法終止 Worker,以釋放系統資源。
因為未使用的 Workers 會消耗系統資源,可能導致 memory leak 等問題,除了使用 terminate()
方法關閉 Worker 之外,也可以使用 Worker Pool 來重用 Workers,而不是每次都建立新的 Worker,這樣可以減少重複新增 / 銷毀 Workers,提高速度與效能。
terminate()
方法關閉 Worker只要一行語法就能關閉 Worker:
worker.terminate();
console.log('Worker terminated');
對於經常使用的 Workers,可以考慮使用 Worker Pool 來重用 Workers。
class WorkerPool {
constructor(size, script) {
this.pool = [];
for (let i = 0; i < size; i++) {
this.pool.push(new Worker(script));
}
}
getWorker() {
return this.pool.pop();
}
releaseWorker(worker) {
this.pool.push(worker);
}
}
const pool = new WorkerPool(4, 'worker.js');
const worker = pool.getWorker();
worker.postMessage('Hello, Worker!');
worker.onmessage = function(event) {
console.log('Received from Worker:', event.data);
pool.releaseWorker(worker);
};
我們先建立一個 WorkerPool 類別,建構子接收兩個參數:
size
:Worker 的數量script
:這些 Worker 執行的 JavaScript 檔案for
迴圈,根據指定的 size
,建立並儲存 Worker 實例。getWorker()
方法可以讓我們從池中取出要用的 Worker,releaseWorker()
則是將使用完的 Worker 重新放回池中,這樣就能重複利用相同的 Worker,減少浪費。
在看程式碼時,大家可能會疑惑,為什麼我的 size
要設為 4 呢?
如果應用程式只需要執行一個任務,且這個任務不會被分割成多個子任務來同時處理,那我們將 size
設為 1 的確更為合理,因為這樣我們的 Worker Pool 就只會有一個 Worker 實例。
但是在一些情境下,即使我們只有一個 worker.js
檔案,還是可能需要多個 Worker 同時處理多個任務,這時候使用多個 Worker 就有意義了。例如在處理大量數據資料或圖片時,我們可以將資料分成多個部分,並分配給不同的 Worker 同時處理,以加快處理的速度。
以上面的程式碼為例,我們設定 size = 4
,這時候的 Worker Pool 會包含 4 個不同的 Worker 實例,但它們都會執行相同的 worker.js
腳本。但我們並不需要關心取到的是哪一個 Worker,因為它們執行的代碼和邏輯都是相同的。
我們只要知道,每次呼叫 getWorker()
時,會從 Worker Pool 取出一個可用的 Worker。這個 Worker 將會被用來執行一個任務,而在任務完成後,我們再呼叫 releaseWorker(worker)
將它放回池中,這樣其他任務就可以再次使用它。
這個大家應該看到不想看了 XXD,我在前面的章節,或是這篇文章,都反覆的使用這個情境來做例子,請容我再重提一次:當處理大量資料數據或圖片時,可以將任務分割並分配給不同的 Worker 進行處理,而 Worker Pool 可以幫助我們有效管理這些 Worker,確保資源被合理分配使用。
提到「資源被合理分配使用」,就來講講我們還能幫 Worker Pool 設定更複雜的分配方法,例如負載平衡,根據每個 Worker 的工作量動態分配任務,避免某些 Worker 過度工作、某些 Worker 閒閒沒事做。
以下是一個簡單的程式碼範例:
class WorkerPool {
constructor(size, script) {
this.pool = [];
this.taskQueue = [];
for (let i = 0; i < size; i++) {
const worker = new Worker(script);
worker.isBusy = false; // 標記這個 Worker 是否在忙碌中
this.pool.push(worker);
}
}
getAvailableWorker() {
return this.pool.find(worker => !worker.isBusy);
}
assignTask(task) {
const worker = this.getAvailableWorker();
if (worker) {
worker.isBusy = true;
worker.postMessage(task);
worker.onmessage = (event) => {
console.log('Task completed:', event.data);
worker.isBusy = false;
// 如果有排隊的任務,分配給這個空閒的 Worker
if (this.taskQueue.length > 0) {
this.assignTask(this.taskQueue.shift());
}
};
} else {
// 如果沒有可用的 Worker,將任務加入排隊隊列
this.taskQueue.push(task);
}
}
}
// 範例:建立一個 WorkerPool,並分配任務
const pool = new WorkerPool(4, 'worker.js');
// 分配多個任務給 WorkerPool,如果所有 Worker 都很忙碌,這個任務將加入排隊行列
pool.assignTask('Task 1');
pool.assignTask('Task 2');
pool.assignTask('Task 3');
pool.assignTask('Task 4');
pool.assignTask('Task 5');
如果需要限制同時執行的 Worker 數量,也可以利用 WorkerPool 管理和限制這些資源:
class WorkerPool {
constructor(size, script) {
this.pool = [];
this.taskQueue = [];
for (let i = 0; i < size; i++) {
this.pool.push(new Worker(script));
}
this.activeWorkers = 0;
this.maxWorkers = size;
}
runTask(task) {
if (this.activeWorkers < this.maxWorkers) {
const worker = this.pool.pop();
this.activeWorkers++;
worker.postMessage(task);
worker.onmessage = (event) => {
console.log('Task completed:', event.data);
this.activeWorkers--;
this.pool.push(worker);
if (this.taskQueue.length > 0) {
this.runTask(this.taskQueue.shift());
}
};
} else {
this.taskQueue.push(task);
}
}
}
// 範例:建立一個 WorkerPool,並限制同時執行的 Worker 數量
const pool = new WorkerPool(2, 'worker.js'); // 最多同時運行2個 Worker
// 分配多個任務
pool.runTask('Task 1');
pool.runTask('Task 2');
pool.runTask('Task 3'); // 這個任務將在前兩個任務完成後才會開始
pool.runTask('Task 4'); // 這個任務將在第三個任務完成後才會開始
Web Workers API 不是一個很新的技術,但知道他的人還蠻少的,希望透過這三篇文章介紹,能讓大家更了解 Web Workers API 的功能與應用情境,未來如果真的有需要處理大量資料,也歡迎使用 Web Workers API 來協助處理唷。