1. 引言
併發任務執行是多執行緒程式設計中的一個重要主題,當應用程式需要同時處理多個任務時,如果不加以控制,可能會導致資源過度消耗或系統負載過高。通俗的講,就是前端有一萬個請求,後端一次只能處理五十個,那麼要如何控制併發確保系統穩定執行呢?對於前端開發來說,js是單執行緒機制已經是一個偽命題了,我們透過非同步操作,可以合理地限制併發任務的數量,確保系統穩定執行,同時提高資源利用率。 下面是設計思路以及具體實現:
2. 設計併發任務控制器
我們的目標是建立一個類 SuperTask
,它可以接受任務並將它們按照指定的併發數量進行排程執行。每個任務都是一個返回 Promise
的函式,這樣我們可以輕鬆地處理非同步操作。
2.1 建構函式
類中新增建構函式,初始化 SuperTask
物件的屬性:
parallelCount
:使用者可以透過傳入引數來設定最大併發數量。預設值為2,用於接下來的測試。tasks
:一個數組,用於儲存等待執行的任務物件。runningCount
:一個計數器,記錄當前正在執行的任務數量。
class SuperTask { constructor(parallelCount = 2) { this.parallelCount = parallelCount; // 併發量 this.tasks = []; // 待執行的任務佇列 this.runningCount = 0; // 正在執行的任務數量 } }
2.2 add
方法
類中新增add
方法,接收一個任務(即返回 Promise
的函式),將其封裝成一個任務物件,並新增到任務佇列中。之後呼叫 _run
方法嘗試執行佇列中的任務。
// 新增任務到佇列 add(task) { return new Promise((resolve, reject) => { this.tasks.push({ task, resolve, reject }); this._run(); }); }
2.3 _run
方法
類中新增_run
方法,這是一個私有方法,用於檢查是否有可用的併發槽位,並執行佇列中的任務。它的工作流程如下:
檢查當前正在執行的任務數量是否小於最大併發數量。
如果小於最大併發數量,並且任務佇列中有任務,則從佇列中取出一個任務並執行。
執行任務後,無論結果如何(成功或失敗),都會呼叫
.finally()
回撥來減少正在執行的任務數量,並再次呼叫_run
方法嘗試執行佇列中的下一個任務。
// 執行任務佇列中的任務 _run() { while (this.runningCount < this.parallelCount && this.tasks.length > 0) { const { task, resolve, reject } = this.tasks.shift(); this.runningCount++; task().then(resolve, reject).finally(() => { this.runningCount--; this._run(); }); } }
2.4 使用示例
假設我們有一個簡單的延時函式 timeout
,用於模擬非同步操作。
function timeout(time) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, time); }); }
接下來,我們可以建立一個 SuperTask
例項,並新增一些任務:
const superTask = new SuperTask(); function addTask(time, name) { superTask.add(() => timeout(time)).then(() => { console.log(`任務${name}完成`); }); } addTask(10000, '1'); addTask(2000, '2'); addTask(5000, '3'); addTask(1000, '4'); addTask(7000, '5'); addTask(3000, '6');
總結
併發任務控制器 SuperTask
,透過限制併發任務的數量來確保系統的穩定性和效率。該控制器透過維護一個任務佇列和一個執行計數器,確保在不超過設定的最大併發數量的前提下,有序地執行任務,從而實現高效的併發控制。併發任務控制器的核心在於_run()
的設計,即把任務都裝在任務佇列裡,只取併發量的任務,某任務結束之後,透過遞迴再從佇列裡取任務,希望友友們在面試中遇到這類問題可以有一定的思路。