前言
學習過 JavaScript 的可能會了解,JavaScript 的宿主瀏覽器只有一個執行緒執行 JavaScript,除了 JavaScript 的執行緒,瀏覽器中單個頁面還有一些其他執行緒,例如:UI 執行緒負責處理渲染 DOM 元素;GUI 執行緒用於處理與使用者互動的邏輯;網路執行緒用於傳送接收 HTTP 請求;file 執行緒用於讀取檔案;定時器執行緒處理定時任務等等。
單執行緒原因為什麼不能像很多高階語言一樣支援多執行緒呢?假定 JavaScript 同時有兩個執行緒,一個執行緒在HTML中建立了一個標籤元素,另一個執行緒刪除了這個標籤,這時瀏覽器應該執行什麼操作?瀏覽器中 JavaScript 的主要用途是操作 DOM 。這決定了它只能是單執行緒,否則會帶來很複雜的同步問題。為了避免複雜性,大部分主流瀏覽器的 JavaScript 執行環境只支援單執行緒。
JavaScript 的事件驅動既然 JavaScript 只支援單執行緒,那麼有人可能會好奇為什麼瀏覽器中的 JavaScript 可以同時傳送多個網路請求或者執行多個事件回撥函式呢?
這是因為 JavaScript 是基於事件驅動,當需要進行網路請求時,JavaScript 執行緒會把請求傳送給 network 執行緒執行,並等待執行結果;當進行檔案讀取時則呼叫 file 執行緒,然後等待結果。然後 JavaScript 會一直輪詢事件庫 event loop,直到有事件完成,這時瀏覽器會驅動 JavaScript 去執行事件的回撥函式。這就是 JavaScript 的事件驅動模型。
web worker誕生單執行緒的最大問題是不能利用多核 CPU 的優點,HTML5 推出的 Web Worker 標準,允許 JavaScript 建立多執行緒,但是子執行緒受主執行緒約束,且不得操作 DOM 。所以,這個新標準不會產生多執行緒同步的問題。
適用場景Web Worker 能解決傳統的 JavaScript 單執行緒出現的執行阻塞問題,因而適合以下幾種業務場景:
平行計算;ajax 輪詢;耗時的函式執行;資料預處理/載入。函式介紹1、建立
初始化一個 Web Worker,由於不是所有的瀏覽器都支援 Web Worker,所以需要判斷一下瀏覽器是否支援:
if (window.Worker) {//判斷瀏覽器是否支援web worker var worker = new Worker('test.js');//建立一個執行緒,引數為需要執行的JavaScript檔案}
2 、向執行緒傳遞引數
新的執行緒的上下文環境跟原宿主環境相對獨立的,所以變數作用域不同,如果需要互相讀取變數的話需要透過訊息傳送的方式傳輸變數,例如:
worker.postMessage('test'); //資料型別可以是字串worker.postMessage({method: 'echo', args: ['Work']});//資料型別可以是物件
3 、主執行緒接受訊息
跟上述場景類似,主執行緒也需要透過監聽的方式獲取輔執行緒的訊息:
worker.onmessage = function (event) { console.log('接收到訊息: ' + event.data);}
4 、執行緒載入指令碼
子執行緒內部也可以透過函式載入其他指令碼:
importScripts('script1.js','script2.js');
5 、關閉執行緒
// 主執行緒中關閉子執行緒worker.terminate();// 子執行緒關閉自身self.close();
使用 JavaScript 多執行緒實現非阻塞全排列1 、什麼是全排列
從 n 個不同元素中任取 m(m≤n)個元素,按照一定的順序排列起來,叫做從 n 個不同元素中取出 m 個元素的一個排列。當 m=n 時所有的排列情況叫全排列。
2 、為什麼使用多執行緒處理
這裡並非突出使用 JavaScript 實現全排列的優勢,而是在實際專案中類似這種科學運算相關的演算法可能會消耗一定的 CPU,由於 JavaScript 是解釋型語言,運算效能是它的弱項,而且瀏覽器中執行的 JavaScript 又是單執行緒的,所以一旦出現效能問題可能會導致執行緒阻塞,阻塞之後會導致頁面卡頓,非常影響使用者體驗。使用 webworker 的多執行緒功能將這個運算函式單獨 fork 出一個子執行緒去執行,執行完成之後傳送結果給主執行緒,可以有效的避免效能問題。
3 、程式碼示例
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>JavaScript實現全排列</title><script type="text/JavaScript">function combine() {//點選按鈕向webworker執行緒傳送請求 var worker = new Worker('http://wiki-code.oss-cn-beijing.aliyuncs.com/html5/js/worker.js'); worker.postMessage(document.getElementById("str").value); worker.onmessage= function (event) { document.getElementById("result").innerHTML = event.data ; //監聽JavaScript執行緒的結果 };}</script></head><body> <input type="text" id="str" /> <button onclick="combine()">全排列</button> 結果是:<div id="result" style="width:500px;height:500px;word-break: break-all;"></div></body></html>
worker.js 程式碼如下:
function getGroup(data, index = 0, group = []) {//生成全排列 var need_apply = new Array(); need_apply.push(data[index]); for(var i = 0; i < group.length; i++) { need_apply.push(group[i] + data[index]); } group.push.apply(group, need_apply); if(index + 1 >= data.length) return group; else return getGroup(data, index + 1, group);}onmessage = function(message){//監聽主執行緒的資料請求 var msg = message.data; if(msg == "") postMessage("請輸入正確的字串"); else { var data = msg.split("");//將字串轉陣列 postMessage(getGroup(data)); }}
上述程式碼實現了一個使用 JavaScript 的 Web Worker 實現的全排列的功能。上半部分是主執行緒的程式碼,主要實現了建立子執行緒、傳送資料給子執行緒、接收子執行緒的訊息這幾個功能;下半部分是子執行緒,子執行緒主要負責運算,並將運算結果傳送給主執行緒。
總結早期的 JavaScript 由於考慮操作 DOM 的一致性問題,以及當時的網頁沒有過多的互動所以不需要大量的計算,所以只支援單執行緒。這在多核 CPU 時代的劣勢愈發明顯,所以 HTML5 中推出多執行緒解決這個問題。回顧本章主要介紹了 Web Worker 的使用方式以及其適用場景。
更多詳細內容:https://developer.mozilla.org/zh-CN/docs/Web/API/Worker