iT邦幫忙

2024 iThome 鐵人賽

DAY 15
0
JavaScript

可愛又迷人的 Web API系列 第 15

Day15. Web Workers API 的生命週期

  • 分享至 

  • xImage
  •  

上一篇文章提到:我們需要合理的使用 Worker,盡量重用已有的 Worker 實例,避免不斷建立新的實例;此外,在不需要用到 Worder 時,使用 terminate 方法終止 Worker,以釋放系統資源。

因為未使用的 Workers 會消耗系統資源,可能導致 memory leak 等問題,除了使用 terminate() 方法關閉 Worker 之外,也可以使用 Worker Pool 來重用 Workers,而不是每次都建立新的 Worker,這樣可以減少重複新增 / 銷毀 Workers,提高速度與效能。

使用 terminate() 方法關閉 Worker

只要一行語法就能關閉 Worker:

worker.terminate();
console.log('Worker terminated');

Worker Pool 介紹與範例

對於經常使用的 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,減少浪費。

Worker Pool 的設計和使用場景

在看程式碼時,大家可能會疑惑,為什麼我的 size 要設為 4 呢?

如果應用程式只需要執行一個任務,且這個任務不會被分割成多個子任務來同時處理,那我們將 size 設為 1 的確更為合理,因為這樣我們的 Worker Pool 就只會有一個 Worker 實例。

但是在一些情境下,即使我們只有一個 worker.js 檔案,還是可能需要多個 Worker 同時處理多個任務,這時候使用多個 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 任務的方法

提到「資源被合理分配使用」,就來講講我們還能幫 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 來協助處理唷。


上一篇
Day14. Web Workers API 的限制與效能優化處理
下一篇
Day16. 自己架個 WebSocket Server 玩玩吧
系列文
可愛又迷人的 Web API31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言