国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

使用 Web Worker 實現簡單的非阻塞異步

junfeng777 / 1197人閱讀

摘要:只會把一個函數延后執行,但還是在主線程中執行,執行函數的時候會阻塞線程。規范并沒有定義多線程,至今也沒有原生的多線程實現。然而在中卻定義了用于實現瀏覽器中的多線程。使用也非常簡單,只需要預先在中注冊事件,在主線程中給處理就好了。

之前的文章提到了 JavaScript 中的異步編程,然而無論早就存在的 setTimeout 還是 ES6 中的 Promise,它們都是 阻塞 異步,執行函數的時候,會阻塞線程。setTimeout 只會把一個函數延后執行,但還是在主線程中執行,執行函數的時候會阻塞線程。換句話說,setTimeout 只實現了過程間并發(concurrent)而未實現并行(parallel)。

ES 規范并沒有定義多線程,Node.js 至今也沒有原生的多線程實現。然而在 HTML5 中卻定義了 Web Worker 用于實現瀏覽器中的多線程。

Web Worker

引用 MDN 原文:

Web Workers 使得一個Web應用程序可以在與主執行線程分離的后臺線程中運行一個腳本操作。這樣做的好處是可以在一個多帶帶的線程中執行費時的處理任務,從而允許主(通常是UI)線程運行而不被阻塞/放慢。

與樸素(原始)的多線程編程方式不同,Web Worker 通常不允許線程間共享數據,所以沒有線程同步、數據競爭等問題,更沒有沒有鎖(Mutex)和條件變量(Condition variable)等概念(注 1)。它們使用 postMessage 相互通信,可以認為是 JS 中的參與者模式實現。各個 Worker 間數據獨立,不共享內存:postMessage 始終通過結構化克隆的方式深拷貝傳值。

使用 Web Worker 也非常簡單,只需要預先在 Worker 中注冊 message 事件,在主線程中 postMessage 給 Worker 處理就好了。處理完后可以再通過 postMessage 傳結果給主線程。

需要注意的是,Web Worker 中不可以操作 DOM,一切與 DOM 操作相關的函數、類都不能使用(創建一個 DOM 元素發回給主線程 appendChild 也不行),所以可以使用的方法非常有限,只適用于處理數據(注 2)。

使用 Web Worker 實現非阻塞的 Promise

前面提到 Promise 是阻塞異步,那是否可以把要處理的數據轉發給某個 Worker 處理并返回一個 Promise,在處理完后將其 resolve 掉呢?

答案當然是可以的,而且實現并不復雜。

創建 Web Worker

首先當然是 new 一個 Worker 出來。需要注意的是 Worker 的構造函數 接受的是一個 JavaScript 腳本的 URL,可否接受 data-uri 看瀏覽器,實測 Chrome、Firefox 可以,Safari、Edge 不行(會拋 SECURITY_ERR 異常)。

簡單起見,這里還是采取 data-uri 的形式。考慮可移植性的話可以先指定一個靜態文件,然后使用 postMessage 把函數體傳過去。

this._worker = new Worker("data:text/javascript," + encodeURIComponent(`"use strict";
const __fn = ${fn};
onmessage = e => postMessage(__fn(...e.data));`));

Worker 中做了兩件事:

定義一個函數變量 __fn,其值 fn 是需要執行的函數。如果 fn 本身是一個函數對象,這里將其轉換為字符串,相當于把函數的源代碼拼到了字符串里。

綁定 message 事件。將傳入的值作為參數列表調用 __fn,然后將 __fn 的返回值通過 postMessage 傳給主函數。

當接受請求時,派發事件給創建的 Worker
function dispatch(...args) {
  return new Promise((resolve, reject) => {
    this._queue.push({ resolve, reject });
    this._worker.postMessage(args);
  });
}

返回一個 Promise。注意這里不能只是簡單的 postMessage。因為如果使用者多次調用 dispatch 函數一次創建了多個 Promise,之后很難確定是哪個 Promise 完成了。這里通過一個隊列記憶創建的 Promise 順序,然后依次 resolve(單個 Worker 處理 message 事件還是順序執行的)。當然你也可以多傳一個標記值給 Worker 用于標記被 resolve 的 Promise。

JavaScript 里的隊列就是數組:

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 表示出現了異常。對應的 Promise 的 resolve 和 reject 直接從隊列里取出來。

完整代碼
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 => { // 創建對象,把入口函數傳入
  for (let i=0; i<1000; ++i) arr.sort(); // 耗費些時間
  return arr;  // 返回處理后的結果
});

const arr = Array.from({ length: 8192 }, () => Math.random() * 10000); // 需要處理的數據
dispatcher.dispatch(arr)  // 派發給 Worker
  .then(res => console.log(res));  // 處理完畢后輸出

在瀏覽器中測試,會生成這樣一段代碼:

排序大數組 1000 次的同時 UI 響應仍然不受影響。

這里還有一個線程池的版本,可以創建多個 Worker 同時并行執行多個任務:https://github.com/CarterLi/T...

因為要區分究竟是哪個 Worker 完成運行,處理 Worker 返回值的邏輯復雜了一些,有什么建議歡迎提出。

注 1:ES2017 中加入 SharedArrayBuffer 后已經可以在主線程和各 Web Worker 間共享數據,使用 Atomics.wait()Atomics.wake() 還可以實現傳統意義上的鎖和條件變量。但由于其出現較晚且并非使用 Web Worker 的主流方式,這里不展開討論。

注 2:還有一個可能是在 Worker 中畫圖,見 OffscreenCanvas。一旦實現,對游戲編程是個不小的幫助。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/90395.html

相關文章

  • 深入 Nginx 之架構篇

    摘要:請求的多階段異步處理多階段異步處理請求與事件驅動架構是密切相關的,也就是說,請求的多階段異步處理只能基于事件驅動架構實現。 前言 最近在讀 Nginx 相關的書籍,做一下讀書筆記。 Nginx 作為業界知名的高性能服務器,被廣泛的應用。它的高性能正是由于其優秀的架構設計,其架構主要包括這幾點:模塊化設計、事件驅動架構、請求的多階段異步處理、管理進程與多工作進程設計、內存池的設計,以下內...

    linkin 評論0 收藏0
  • 深入 Nginx 之架構篇

    摘要:請求的多階段異步處理多階段異步處理請求與事件驅動架構是密切相關的,也就是說,請求的多階段異步處理只能基于事件驅動架構實現。 前言 最近在讀 Nginx 相關的書籍,做一下讀書筆記。 Nginx 作為業界知名的高性能服務器,被廣泛的應用。它的高性能正是由于其優秀的架構設計,其架構主要包括這幾點:模塊化設計、事件驅動架構、請求的多階段異步處理、管理進程與多工作進程設計、內存池的設計,以下內...

    zhangrxiang 評論0 收藏0
  • 深入理解Node.js 進程與線程(8000長文徹底搞懂)

    摘要:在單核系統之上我們采用單進程單線程的模式來開發。由進程來管理所有的子進程,主進程不負責具體的任務處理,主要工作是負責調度和管理。模塊與模塊總結無論是模塊還是模塊,為了解決實例單線程運行,無法利用多核的問題而出現的。 前言 進程與線程是一個程序員的必知概念,面試經常被問及,但是一些文章內容只是講講理論知識,可能一些小伙伴并沒有真的理解,在實際開發中應用也比較少。本篇文章除了介紹概念,通過...

    Harpsichord1207 評論0 收藏0
  • JavaScript 工作原理之七-Web Workers 分類及 5 個使用場景

    摘要:最后,我們將會介紹個的使用場景。異步編程的局限性前面我們了解到異步編程及其使用時機。請求是一個很好的異步編程的使用場景。整個是基于單線程環境的而部分可以突破這方面的限制。最佳使用場景迄今為止,我們列舉了的長處及其限制。 Web Workers 分類及 5 個使用場景 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 這是 JavaScri...

    cartoon 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<