ParallelJS:優雅的Web Worker解決方案
ParallelJS為使用Web Worker時可能出現的問題提供了一種優雅的解決方案,它提供了一個具有便捷抽象和輔助工具的實用API。 HTML5引入的Worker接口允許創建具有較長運行時間和高計算量需求的函數,這些函數可以同時使用以提高網站響應速度。 ParallelJS允許對JavaScript代碼進行並行化處理,利用同時多線程 (SMT) 更有效地使用現代CPU。 ParallelJS庫提供諸如spawn
、map
和reduce
等方法,分別用於並行執行計算、處理數據和聚合碎片化結果。
HTML5帶來的一個最酷的新可能性是Web Workers API的Worker接口。在此之前,我們不得不採用一些技巧來向用戶展示響應迅速的網站。 Worker接口允許我們創建具有較長運行時間和高計算量需求的函數。此外,Worker實例可以同時使用,使我們能夠根據需要生成任意數量的這些工作器。在本文中,我將討論為什麼多線程很重要,以及如何使用ParallelJS在JavaScript中實現它。
為什麼需要多線程?
這是一個合理的問題。從歷史上看,生成線程的能力提供了一種優雅的方式來劃分進程中的工作。操作系統負責調度每個線程的可用時間,這樣優先級更高、工作量更大的線程將優先於低優先級的空閒線程。在過去的幾年裡,同時多線程 (SMT) 已經成為訪問現代CPU計算能力的關鍵。原因很簡單:摩爾定律在每單位面積晶體管數量方面仍然有效。然而,由於多種原因,頻率縮放不得不停止。因此,必須以其他方式使用可用的晶體管。人們決定,架構改進(例如SIMD)和多核代表最佳選擇。
為了使用SMT,我們需要編寫並行代碼,即為獲得單個結果而並行運行的代碼。我們通常需要考慮特殊的算法,因為大多數順序代碼要么很難並行化,要么效率非常低。原因在於Amdahl定律,該定律指出加速比S由下式給出:
其中N是並行工作器的數量(例如處理器、核心或線程),P是並行部分。將來可能會使用更多依賴於並行算法的多核架構。在高性能計算領域,GPU系統和特殊架構(例如英特爾至強Phi)代表了此類平台。最後,我們應該區分一般的並發應用程序或算法和並行執行。並行性是(可能相關的)計算的同時執行。相反,並發是獨立執行進程的組合。
JavaScript中的多線程
在JavaScript中,我們已經知道如何編寫並發程序,即使用回調函數。現在可以將此知識轉移到創建並行程序中!根據其自身的結構,JavaScript是在由事件循環(通常遵循反應器模式)調解的單個線程中執行的。例如,這為我們處理對(外部)資源的異步請求提供了一些很好的抽象。它還保證先前定義的回調始終在相同的執行線程中觸發。沒有與線程相關的跨線程異常、競爭條件或其他問題。但是,這並沒有讓我們更接近JavaScript中的SMT。隨著Worker接口的引入,已經找到了一個優雅的解決方案。從主應用程序的角度來看,Web Worker中的代碼應被視為並發運行的任務。通信也是以這種方式進行的。我們使用消息API,該API也可用於從包含的網站到託管頁面的通信。例如,以下代碼通過向發起者發送消息來響應傳入的消息。
window.addEventListener('message', function (event) { event.source.postMessage('Howdy Cowboy!', event.origin); }, false);
理論上,Web Worker也可以生成另一個Web Worker。但是,實際上大多數瀏覽器禁止這樣做。因此,Web Worker之間通信的唯一方法是通過主應用程序。通過消息進行的通信是並發進行的,因此只有異步(非阻塞)通信。起初,這在編程中可能很奇怪,但它帶來了許多優點。最重要的是,我們的代碼應該沒有競爭條件!讓我們來看一個使用兩個參數表示序列的開始和結束來在後台計算素數序列的簡單示例。首先,我們創建一個名為prime.js的文件,其中包含以下內容:
onmessage = function (event) { var arguments = JSON.parse(event.data); run(arguments.start, arguments.end); }; function run (start, end) { var n = start; while (n < end) { var k = Math.sqrt(n); var found = false; for (var i = 2; !found && i <= k; i++) { found = n % i === 0; } if (!found) { postMessage(n.toString()); } n++; } }
現在,我們只需要在主應用程序中使用以下代碼來啟動後台工作器即可。
if (typeof Worker !== 'undefined') { var w = new Worker('prime.js'); w.onmessage = function(event) { console.log(event); }; var args = { start : 100, end : 10000 }; w.postMessage(JSON.stringify(args)); }
相當多的工作。尤其令人討厭的是使用另一個文件。這產生了很好的分離,但對於較小的任務似乎完全是多餘的。幸運的是,有一種解決方法。考慮以下代碼:
var fs = (function () { /* code for the worker */ }).toString(); var blob = new Blob( [fs.substr(13, fs.length - 14)], { type: 'text/javascript' } ); var url = window.URL.createObjectURL(blob); var worker = new Worker(url); // Now setup communication and rest as before
當然,我們可能希望有一個比這樣的幻數(13和14)更好的解決方案,並且根據瀏覽器,必須使用Blob和createObjectURL的回退。如果您不是JavaScript專家,fs.substr(13, fs.length - 14)的作用是提取函數體。我們通過將函數聲明轉換為字符串(使用toString()調用)並刪除函數本身的簽名來做到這一點。
ParallelJS能幫上忙嗎?
這就是ParallelJS發揮作用的地方。它為一些便利以及Web Worker提供了一個不錯的API。它包括許多輔助工具和非常有用的抽象。我們首先提供一些要處理的數據。
var p = new Parallel([1, 2, 3, 4, 5]); console.log(p.data);
data字段產生提供的數組。還沒有調用任何“並行”操作。但是,實例p包含一組方法,例如spawn,它將創建一個新的Web Worker。它返回一個Promise,這使得使用結果變得輕而易舉。
window.addEventListener('message', function (event) { event.source.postMessage('Howdy Cowboy!', event.origin); }, false);
上面代碼的問題是計算不會真正並行。我們只創建一個單個後台工作器,它一次性處理整個數據數組。只有在處理完整個數組後,我們才能獲得結果。更好的解決方案是使用Parallel實例的map函數。
onmessage = function (event) { var arguments = JSON.parse(event.data); run(arguments.start, arguments.end); }; function run (start, end) { var n = start; while (n < end) { var k = Math.sqrt(n); var found = false; for (var i = 2; !found && i <= k; i++) { found = n % i === 0; } if (!found) { postMessage(n.toString()); } n++; } }
在前面的示例中,核心非常簡單,可能過於簡單。在一個真實的示例中,將涉及許多操作和函數。我們可以使用require函數包含引入的函數。
if (typeof Worker !== 'undefined') { var w = new Worker('prime.js'); w.onmessage = function(event) { console.log(event); }; var args = { start : 100, end : 10000 }; w.postMessage(JSON.stringify(args)); }
reduce函數有助於將碎片化的結果聚合到單個結果中。它提供了一個方便的抽象,用於收集子結果並在知道所有子結果後執行某些操作。
結論
ParallelJS為我們提供了一種優雅的方式來規避使用Web Worker時可能出現的問題。此外,我們獲得了一個包含一些有用抽象和輔助工具的不錯的API。將來可以集成進一步的改進。除了能夠在JavaScript中使用SMT之外,我們可能還想使用矢量化功能。如果支持,SIMD.js似乎是一種可行的方法。在某些(希望不會太遙遠的)將來,使用GPU進行計算也可能是一個有效的選項。在Node.js中存在CUDA(一種並行計算架構)的包裝器,但是仍然無法執行原始JavaScript代碼。在那之前,ParallelJS是我們充分利用多核CPU處理長時間運行計算的最佳選擇。你呢?你如何使用JavaScript釋放現代硬件的強大功能?
關於使用ParallelJS的並行JavaScript的常見問題解答 (FAQ)
ParallelJS是一個JavaScript庫,允許您通過利用多核處理器來並行化數據處理。它的工作原理是創建一個新的Parallel對象並將一個數據數組傳遞給它。然後可以使用.map()
方法並行處理此數據,該方法將指定的函數應用於數組中的每個項目。然後在新的數組中返回結果。
可以使用npm(Node.js包管理器)安裝ParallelJS。只需在終端中運行命令“npm install paralleljs”。安裝完成後,您可以使用“var Parallel = require('paralleljs');”在您的JavaScript文件中引用它。
ParallelJS允許您充分利用多核處理器進行數據處理任務。這可以大大加快大型數據集的處理時間。它還提供了一個簡單直觀的API,使並行化代碼變得容易。
是的,ParallelJS可以在瀏覽器中使用。您可以使用腳本標籤和ParallelJS文件的URL將其包含在HTML文件中。包含後,您可以像在Node.js中一樣使用Parallel對象。
.map()
方法? ParallelJS中的.map()
方法用於將函數應用於數據數組中的每個項目。該函數作為字符串傳遞給.map()
方法。然後在新的數組中返回結果。例如,“var p = new Parallel([1, 2, 3]); p.map('function(n) { return n * 2; }');”將返回一個值為[2, 4, 6]的新數組。
.reduce()
方法是什麼? ParallelJS中的.reduce()
方法用於使用指定的函數將數據數組減少為單個值。該函數作為字符串傳遞給.reduce()
方法。例如,“var p = new Parallel([1, 2, 3]); p.reduce('function(a, b) { return a b; }');”將返回值6。
是的,ParallelJS中的方法可以鏈接在一起。例如,您可以使用.map()
方法處理數據,然後使用.reduce()
方法將結果組合成單個值。
可以使用.catch()
方法處理ParallelJS中的錯誤。此方法接受一個函數,如果在處理過程中發生錯誤,則會調用該函數。錯誤對象將傳遞給此函數。
是的,ParallelJS可以與其他JavaScript庫一起使用。但是,您需要確保使用.require()
方法將庫包含在worker上下文中。
雖然ParallelJS可以大大加快大型數據集的處理時間,但它可能並非所有任務的最佳選擇。對於小型數據集,創建worker和傳輸數據的開銷可能超過並行化的益處。最好使用您的具體用例測試ParallelJS,以查看它是否提供了性能優勢。
以上是並聯JavaScript的詳細內容。更多資訊請關注PHP中文網其他相關文章!