摘要:只會(huì)把一個(gè)函數(shù)延后執(zhí)行,但還是在主線程中執(zhí)行,執(zhí)行函數(shù)的時(shí)候會(huì)阻塞線程。規(guī)范并沒有定義多線程,至今也沒有原生的多線程實(shí)現(xiàn)。然而在中卻定義了用于實(shí)現(xiàn)瀏覽器中的多線程。使用也非常簡單,只需要預(yù)先在中注冊事件,在主線程中給處理就好了。
之前的文章提到了 JavaScript 中的異步編程,然而無論早就存在的 setTimeout 還是 ES6 中的 Promise,它們都是 阻塞 異步,執(zhí)行函數(shù)的時(shí)候,會(huì)阻塞線程。setTimeout 只會(huì)把一個(gè)函數(shù)延后執(zhí)行,但還是在主線程中執(zhí)行,執(zhí)行函數(shù)的時(shí)候會(huì)阻塞線程。換句話說,setTimeout 只實(shí)現(xiàn)了過程間并發(fā)(concurrent)而未實(shí)現(xiàn)并行(parallel)。
ES 規(guī)范并沒有定義多線程,Node.js 至今也沒有原生的多線程實(shí)現(xiàn)。然而在 HTML5 中卻定義了 Web Worker 用于實(shí)現(xiàn)瀏覽器中的多線程。
Web Worker引用 MDN 原文:
Web Workers 使得一個(gè)Web應(yīng)用程序可以在與主執(zhí)行線程分離的后臺(tái)線程中運(yùn)行一個(gè)腳本操作。這樣做的好處是可以在一個(gè)多帶帶的線程中執(zhí)行費(fèi)時(shí)的處理任務(wù),從而允許主(通常是UI)線程運(yùn)行而不被阻塞/放慢。
與樸素(原始)的多線程編程方式不同,Web Worker 通常不允許線程間共享數(shù)據(jù),所以沒有線程同步、數(shù)據(jù)競爭等問題,更沒有沒有鎖(Mutex)和條件變量(Condition variable)等概念(注 1)。它們使用 postMessage 相互通信,可以認(rèn)為是 JS 中的參與者模式實(shí)現(xiàn)。各個(gè) Worker 間數(shù)據(jù)獨(dú)立,不共享內(nèi)存:postMessage 始終通過結(jié)構(gòu)化克隆的方式深拷貝傳值。
使用 Web Worker 也非常簡單,只需要預(yù)先在 Worker 中注冊 message 事件,在主線程中 postMessage 給 Worker 處理就好了。處理完后可以再通過 postMessage 傳結(jié)果給主線程。
需要注意的是,Web Worker 中不可以操作 DOM,一切與 DOM 操作相關(guān)的函數(shù)、類都不能使用(創(chuàng)建一個(gè) DOM 元素發(fā)回給主線程 appendChild 也不行),所以可以使用的方法非常有限,只適用于處理數(shù)據(jù)(注 2)。
使用 Web Worker 實(shí)現(xiàn)非阻塞的 Promise前面提到 Promise 是阻塞異步,那是否可以把要處理的數(shù)據(jù)轉(zhuǎn)發(fā)給某個(gè) Worker 處理并返回一個(gè) Promise,在處理完后將其 resolve 掉呢?
答案當(dāng)然是可以的,而且實(shí)現(xiàn)并不復(fù)雜。
創(chuàng)建 Web Worker首先當(dāng)然是 new 一個(gè) Worker 出來。需要注意的是 Worker 的構(gòu)造函數(shù) 接受的是一個(gè) JavaScript 腳本的 URL,可否接受 data-uri 看瀏覽器,實(shí)測 Chrome、Firefox 可以,Safari、Edge 不行(會(huì)拋 SECURITY_ERR 異常)。
簡單起見,這里還是采取 data-uri 的形式。考慮可移植性的話可以先指定一個(gè)靜態(tài)文件,然后使用 postMessage 把函數(shù)體傳過去。
this._worker = new Worker("data:text/javascript," + encodeURIComponent(`"use strict"; const __fn = ${fn}; onmessage = e => postMessage(__fn(...e.data));`));
Worker 中做了兩件事:
定義一個(gè)函數(shù)變量 __fn,其值 fn 是需要執(zhí)行的函數(shù)。如果 fn 本身是一個(gè)函數(shù)對象,這里將其轉(zhuǎn)換為字符串,相當(dāng)于把函數(shù)的源代碼拼到了字符串里。
綁定 message 事件。將傳入的值作為參數(shù)列表調(diào)用 __fn,然后將 __fn 的返回值通過 postMessage 傳給主函數(shù)。
當(dāng)接受請求時(shí),派發(fā)事件給創(chuàng)建的 Workerfunction dispatch(...args) { return new Promise((resolve, reject) => { this._queue.push({ resolve, reject }); this._worker.postMessage(args); }); }
返回一個(gè) Promise。注意這里不能只是簡單的 postMessage。因?yàn)槿绻褂谜叨啻握{(diào)用 dispatch 函數(shù)一次創(chuàng)建了多個(gè) Promise,之后很難確定是哪個(gè) Promise 完成了。這里通過一個(gè)隊(duì)列記憶創(chuàng)建的 Promise 順序,然后依次 resolve(單個(gè) Worker 處理 message 事件還是順序執(zhí)行的)。當(dāng)然你也可以多傳一個(gè)標(biāo)記值給 Worker 用于標(biāo)記被 resolve 的 Promise。
JavaScript 里的隊(duì)列就是數(shù)組:
this._queue = [];接收 Worker 處理完返回的值
this._worker.onmessage = e => this._queue.shift().resolve(e.data); this._worker.onerror = e => this._queue.shift().reject(e.error);
onmessage 表示正常返回;onerror 表示出現(xiàn)了異常。對應(yīng)的 Promise 的 resolve 和 reject 直接從隊(duì)列里取出來。
完整代碼class Dispatcher { constructor(fn) { this._queue = []; this._worker = new Worker("data:text/javascript," + encodeURIComponent(`"use strict"; const __fn = ${fn}; onmessage = e => postMessage(__fn(...e.data));`)); this._worker.onmessage = e => this._queue.shift().resolve(e.data); this._worker.onerror = e => this._queue.shift().reject(e.error); } dispatch(...args) { return new Promise((resolve, reject) => { this._queue.push({ resolve, reject }); this._worker.postMessage(args); }); } }
這就是完整代碼了,總共不到 20 行。使用的話也很簡單:
const dispatcher = new Dispatcher(arr => { // 創(chuàng)建對象,把入口函數(shù)傳入 for (let i=0; i<1000; ++i) arr.sort(); // 耗費(fèi)些時(shí)間 return arr; // 返回處理后的結(jié)果 }); const arr = Array.from({ length: 8192 }, () => Math.random() * 10000); // 需要處理的數(shù)據(jù) dispatcher.dispatch(arr) // 派發(fā)給 Worker .then(res => console.log(res)); // 處理完畢后輸出
在瀏覽器中測試,會(huì)生成這樣一段代碼:
排序大數(shù)組 1000 次的同時(shí) UI 響應(yīng)仍然不受影響。
完這里還有一個(gè)線程池的版本,可以創(chuàng)建多個(gè) Worker 同時(shí)并行執(zhí)行多個(gè)任務(wù):https://github.com/CarterLi/T...
因?yàn)橐獏^(qū)分究竟是哪個(gè) Worker 完成運(yùn)行,處理 Worker 返回值的邏輯復(fù)雜了一些,有什么建議歡迎提出。
注 1:ES2017 中加入 SharedArrayBuffer 后已經(jīng)可以在主線程和各 Web Worker 間共享數(shù)據(jù),使用 Atomics.wait() 和 Atomics.wake() 還可以實(shí)現(xiàn)傳統(tǒng)意義上的鎖和條件變量。但由于其出現(xiàn)較晚且并非使用 Web Worker 的主流方式,這里不展開討論。
注 2:還有一個(gè)可能是在 Worker 中畫圖,見 OffscreenCanvas。一旦實(shí)現(xiàn),對游戲編程是個(gè)不小的幫助。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/90395.html
摘要:請求的多階段異步處理多階段異步處理請求與事件驅(qū)動(dòng)架構(gòu)是密切相關(guān)的,也就是說,請求的多階段異步處理只能基于事件驅(qū)動(dòng)架構(gòu)實(shí)現(xiàn)。 前言 最近在讀 Nginx 相關(guān)的書籍,做一下讀書筆記。 Nginx 作為業(yè)界知名的高性能服務(wù)器,被廣泛的應(yīng)用。它的高性能正是由于其優(yōu)秀的架構(gòu)設(shè)計(jì),其架構(gòu)主要包括這幾點(diǎn):模塊化設(shè)計(jì)、事件驅(qū)動(dòng)架構(gòu)、請求的多階段異步處理、管理進(jìn)程與多工作進(jìn)程設(shè)計(jì)、內(nèi)存池的設(shè)計(jì),以下內(nèi)...
摘要:請求的多階段異步處理多階段異步處理請求與事件驅(qū)動(dòng)架構(gòu)是密切相關(guān)的,也就是說,請求的多階段異步處理只能基于事件驅(qū)動(dòng)架構(gòu)實(shí)現(xiàn)。 前言 最近在讀 Nginx 相關(guān)的書籍,做一下讀書筆記。 Nginx 作為業(yè)界知名的高性能服務(wù)器,被廣泛的應(yīng)用。它的高性能正是由于其優(yōu)秀的架構(gòu)設(shè)計(jì),其架構(gòu)主要包括這幾點(diǎn):模塊化設(shè)計(jì)、事件驅(qū)動(dòng)架構(gòu)、請求的多階段異步處理、管理進(jìn)程與多工作進(jìn)程設(shè)計(jì)、內(nèi)存池的設(shè)計(jì),以下內(nèi)...
摘要:在單核系統(tǒng)之上我們采用單進(jìn)程單線程的模式來開發(fā)。由進(jìn)程來管理所有的子進(jìn)程,主進(jìn)程不負(fù)責(zé)具體的任務(wù)處理,主要工作是負(fù)責(zé)調(diào)度和管理。模塊與模塊總結(jié)無論是模塊還是模塊,為了解決實(shí)例單線程運(yùn)行,無法利用多核的問題而出現(xiàn)的。 前言 進(jìn)程與線程是一個(gè)程序員的必知概念,面試經(jīng)常被問及,但是一些文章內(nèi)容只是講講理論知識(shí),可能一些小伙伴并沒有真的理解,在實(shí)際開發(fā)中應(yīng)用也比較少。本篇文章除了介紹概念,通過...
摘要:最后,我們將會(huì)介紹個(gè)的使用場景。異步編程的局限性前面我們了解到異步編程及其使用時(shí)機(jī)。請求是一個(gè)很好的異步編程的使用場景。整個(gè)是基于單線程環(huán)境的而部分可以突破這方面的限制。最佳使用場景迄今為止,我們列舉了的長處及其限制。 Web Workers 分類及 5 個(gè)使用場景 原文請查閱這里,略有刪減,本文采用知識(shí)共享署名 4.0 國際許可協(xié)議共享,BY Troland。 這是 JavaScri...
閱讀 3236·2021-11-24 09:39
閱讀 2912·2021-09-09 11:34
閱讀 3189·2021-09-07 09:58
閱讀 2299·2019-08-30 13:07
閱讀 2859·2019-08-29 15:09
閱讀 1560·2019-08-29 13:01
閱讀 2300·2019-08-26 12:18
閱讀 1911·2019-08-26 10:28