您當前的位置:首頁 > 書法

multiple web workers的實現

作者:由 請叫我小飛鵝 發表于 書法時間:2018-08-07

一. 背景

先交代下業務背景,去年十月做了一個影片上傳的相關業務,部分需求如下:

影片檔案的MD5計算

並行上傳,可配置最大並行數。

分片上傳

可隨時中斷,取消上傳。

以上只是上傳部分的功能,對於我這種第一次做上傳的人來說,看了真是一頭霧水。不僅要解決上述的需求,還要考慮其他的設計和效能問題,比如:

js單執行緒:當上傳一個5G+的大檔案,計算MD5的時間約幾分鐘,在主執行緒中無法同時計算多個影片的md5,此後新增的檔案都在佇列中,需要一個一個計算MD5。

並行上傳:js主執行緒基於eventloop,無法做到真正意義上的並行上傳。

分片上傳:切片影片,頻繁的讀寫影片檔案。

維護上傳佇列:當檔案上傳完成或者取消時,自動新增上傳檔案。

對於這種需求,webworker 是最合是不過的了。

所以當時按照如下方式分解了業務功能,解決了上述問腿。

js的主執行緒負責建立web worker,相關UI檢視,更新UI。

worker 負責 檔案計算MD5,切片,上傳,計算相關資料。

處理檔案,上傳時 如需更新UI,worker將相關資料傳遞給主執行緒,主執行緒更新相關UI檢視。

主執行緒需要對檔案 ,上傳 進行計算 和 處理時,通知worker,worker完成相關操作

基於以上,覺得可以把worker定義為一個執行復雜運算的執行緒,將想要執行的方法透過postmessage 方法 傳遞給 worker,當worker接收到後開始執行,並將結果返回給主執行緒。所以寫了個

multi-worker

二.什麼是web worker

在 HTML5 中,

工作執行緒

的出現使得在 Web 頁面中進行多執行緒程式設計成為可能。眾所周知,傳統頁面中(HTML5 之前)的 JavaScript 的執行都是以單執行緒的方式工作的,雖然有多種方式實現了對多執行緒的模擬(例如:JavaScript 中的 setinterval 方法,setTimeout 方法等),但是在本質上程式的執行仍然是由 JavaScript 引擎以單執行緒排程的方式進行的。在 HTML5 中引入的工作執行緒使得瀏覽器端的 JavaScript 引擎可以併發地執行 JavaScript 程式碼,從而實現了對瀏覽器端多執行緒程式設計的良好支援。

HTML5 中的 Web Worker 可以分為兩種不同執行緒型別,一個是專用執行緒 Dedicated Worker,一個是共享執行緒 Shared Worker。兩種型別的執行緒各有不同的用途

web-worker相容性:

multiple web workers的實現

worker中可用的函式和介面

你可以在web worker中使用大多數的標準javascript特性,包括

你可以在web worker中使用大多數的標準javascript特性,包括

Navigator

XMLHttpReque

Array,Date,Math, and String

WindowTimers。setTimeout`and WindowTimers。setInterval

在一個worker中最主要的你不能做的事情就是直接影響父頁面。包括操作父頁面的節點以及使用頁面中的物件。你只能間接地實現,透過self。postMessage回傳訊息給主指令碼,然後從主指令碼那裡執行操作或變化。

特性:

1。為 JavaScript引入真正的執行緒,不必再使用 setTimeout()、setInterval()、XMLHttpRequest 來模擬並行

2。Worker 利用類似執行緒的訊息傳遞實現並行。這非常適合確保對 UI 的重新整理、效能以及對使用者的響應。

3。Web Worker 的三大主要特徵:能夠長時間執行(響應),理想的啟動效能以及理想的記憶體消耗。

適用場景

1。使用專用執行緒進行數學運算

Web Worker最簡單的應用就是用來做後臺計算,而這種計算並不會中斷前臺使用者的操作

2。影象處理

透過使用從或者

3。大量資料的檢索

當需要在呼叫 ajax後處理大量的資料,如果處理這些資料所需的時間長短非常重要,可以在Web Worker中來做這些,避免凍結

UI執行緒

4。背景資料分析

由於在使用Web Worker的時候,我們有更多潛在的CPU可用時間,我們現在可以考慮一下JavaScript中的新應用場景。

限制

1。不能訪問

DOM

BOM

物件(alert不支援,

console。log

部分瀏覽器支援,在safari中不能使用console,否則會報錯)

2。

Location

navigator

的只讀訪問,並且

navigator

封裝成

WorkerNavigator

物件,有部分屬性被更改。

3。無法讀取本地文

4。

全域性變數

中不存在

this

this

並不指向

window

。有

self

,指向

worker

本身

5。子執行緒和

父級執行緒

的通訊是透過值複製,子執行緒對通訊內容的修改,不會影響到主執行緒。在通訊過程中值過大也會影響到效能(解決這個問題可以用

transferable objects

6。條數限制,大多瀏覽器能建立

web worker

執行緒的條數是有限制的,可以手動去拓展,但是如果不設定的話,基本上都在20條以內,每條執行緒大概5M左右,需要手動關掉一些不用的執行緒才能夠建立新的執行緒(相關解決方案)

通訊方法:

傳送訊息

主執行緒 :worker。postMessage();

worker執行緒 :self。postMessage();

接收訊息

主執行緒:worker。message();

worker執行緒

:self。message();

監聽異常

主執行緒:worker。error();

worker執行緒:self。error();

銷燬方法

主執行緒:worker。terminate();

worker執行緒:self。close();

三.API設計

背景需求裡要求實現佇列,所以在multi-worker 裡增加了佇列控制,可以在建立multi-worker例項時配置最大並行執行的worker數量,預設是window。navigator。hardwareConcurrency。

config: {

maxWorkers : (window。navigator && window。navigator。hardwareConcurrency) || 3,

minWorkers : 1

}

對於一個worker的維護佇列主要提供增,刪,查三種方法就夠了,每個worker都會分配一個id,方便我們操作指定worker。每個方法都會返回worker的例項。

add(config = { //增

id:id,

fn:fn,

args:args,

}) {

return new worker(config);

}

getWorker(id) // 查

getIdleWorker(id) //查

removeWorker(id) // 刪

此外,還提供

race

/

all

方法: 返回最先 / 全部 在worker中執行完成的結果。因為postmessage本身是個一來一回的非同步的行為,包裝成promise的肯定更為合適和易用。

all/race(excuFns){

let racePWorkers = [];

let promises = [];

excuFns。forEach((excuFn) => {

let worker = this。add();

racePWorkers。push(worker);

promises。push(worker。reslover。promise);

})

racePWorkers。forEach((worker, index) => {

worker。excu(excuFns[index]。fn, excuFns[index]。args);

});

return Promise。race(promises)

}

worker 方法:

worker只提供一個excu(fn,args)方法,用於執行指定的函式方法。返回一個promise,非同步接收worker中執行的結果。

excu(fn,args){

if(this。busy)throw new Error (`id:${this。id} worker is busy`);

let _fn , _args;

if (fn && typeof fn === ‘function’) {

_fn = GeneralUtils。serializeFunction(fn );

_args = GeneralUtils。stringifyJson(args);

}

this。worker。postMessage({ _fn, _args})

this。busy = true;

return this。reslover。promise;

}

下面我們看一下,具體的使用方法:

const multiWorker = new mWorker({

maxWorkers: 1

});

function recurFib(n) {

if (n == 1 || n == 2) {

return 1;

}

return recurFib(n - 1) + recurFib(n - 2);

}

// 建立指定id的worker,計算

multiWorker。add({id:10})。excu(recurFib,[10])。then((res)=>{

console。log(‘建立指定id worker,計算’);

console。log(res);

document。write

(`Fibonacci(${10}):${res}
`) //——-> output 55

})

//all 方法

var allWorker = multiWorker。all([{ fn: recurFib, args: 20 }, { fn: recurFib, args: 10 }]);

allWorker。then((res) => {

console。log(‘all方法’, res);// [6765,55];

document。write(`all : Fibonacci(${20}),Fibonacci(${10}):${res}
`) //6765,55

})。then(()=>{

//終止 全部worker

setTimeout(() => {

multiWorker。removeWorker();

document。write(`全部worker已銷燬`);

}, 2000)

})

四.實現原理

實現其實蠻簡單的。

web-worker受

同源策略

的限制,Worker 不能讀取本地檔案,所以這個指令碼必須來自網路。透過

worker-loader

在編譯打包時,把本地worker檔案處理。

worker執行緒如何處理主執行緒傳來的方法:主執行緒把需要在worker中執行的方法透過postmessage傳給worker,worker接收到後,透過eval執行此方法,執行結束後,得到結果再透過postmessage傳遞給主執行緒。

web-worker 中 用eval執行主執行緒傳遞的方法

eval(‘(’ + _fn + ‘)’);

五.最佳化

Worker 與“主執行緒”之間的資料傳遞預設是透過結構化克隆(Structured Clone)。但資料量較大時,克隆過程會比較耗時,會影響 postMessage 和 onmessage 函式的執行時間。可以先透過

JSON。stringify

將物件序列化,接收之後再用

JSON。parse

還原。國外大神測試使用

JSON。stringify

JSON。parse

的效能對比。 測試版本有點低,不過能說明使用

stringify

的效能更好一些。

multiple web workers的實現

multiple web workers的實現

multiple web workers的實現

multiple web workers的實現

multiple web workers的實現

(還有一種避開克隆傳值的方法,就是使用Transferable Objects,主要是採用二進位制的儲存方式,採用地址引用,解決資料交換的實時性問題;Transferable Objects支援的常用資料型別有ArrayBuffer,ImageBitmap)

六.總結和問題

寫完發現有幾個類似的庫,實現原理差不多。multi-worker的有點是promise化,增加了佇列和race/all方法。

但是有兩個問題,如果有什麼解決辦法歡迎提意見。

1。由於webworker也是基於eventloop,所以,在worker中執行方法時,無法透過主執行緒的worker。terminate()終止此worker,僅能等待當前任務執行完畢後才能終止。

multiple web workers的實現

2。通postmessage傳進來的函式,無法引用此函式以外的函式,因為在postmessage前,會透過

Json。stringify

序列化。所以有一點雞肋的地方就是我們需要把整段

業務程式碼

全寫在一個方法裡。

參考文獻:

標簽: worker  執行緒  fn  上傳  主線