HTML5 Web Worker深入淺出教程

轉(zhuǎn)載自作者:SerenoShen
原文:https://blog.csdn.net/shenlei19911210/article/details/49779613

HTML5 Web Worker簡(jiǎn)介
2008 年 W3C 制定出第一個(gè) HTML5 草案開(kāi)始到2014年10月28日W3C的HTML工作組正式發(fā)布了HTML5的正式推薦標(biāo)準(zhǔn)(W3C Recommendation)届腐。HTML5 承載了越來(lái)越多嶄新的特性和功能。它不但強(qiáng)化了 Web 系統(tǒng)或網(wǎng)頁(yè)的表現(xiàn)性能洽瞬,而且還增加了對(duì)本地?cái)?shù)據(jù)庫(kù)等 Web 應(yīng)用功能的支持轩端。
這里將會(huì)介紹在 HTML5 中提出的工作線程(Web Worker)的概念帜慢。HTML5規(guī)范指出 Web Worker 的三大主要特征: 能夠長(zhǎng)時(shí)間運(yùn)行(響應(yīng))瘦真,理想的啟動(dòng)性能以及理想的內(nèi)存消耗绰播。Web Worker 允許開(kāi)發(fā)人員編寫(xiě)能夠長(zhǎng)時(shí)間運(yùn)行而不被用戶所中斷的后臺(tái)程序花枫,去執(zhí)行事務(wù)或者邏輯刻盐,并同時(shí)保證頁(yè)面對(duì)用戶的及時(shí)響應(yīng)。本文深入 HTML5 多線程規(guī)范劳翰,講述多線程實(shí)現(xiàn)原理敦锌、方法,同時(shí)以實(shí)例的形式講解 HTML5 中多線程編程以及應(yīng)用磕道。

W3C 中的工作線程規(guī)范到目前為止已經(jīng)定義了出了一系列公共接口供屉,它允許 Web 程序開(kāi)發(fā)人員去創(chuàng)建后臺(tái)線程在他們的主頁(yè)面中并發(fā)的運(yùn)行腳本。這將使得線程級(jí)別的消息通信成為現(xiàn)實(shí)溺蕉。

詳解 HTML5 工作線程原理
傳統(tǒng)上的線程可以解釋為輕量級(jí)進(jìn)程伶丐,它和進(jìn)程一樣擁有獨(dú)立的執(zhí)行控制,一般情況下由操作系統(tǒng)負(fù)責(zé)調(diào)度疯特。而在 HTML5 中的多線程是這樣一種機(jī)制哗魂,它允許在 Web 程序中并發(fā)執(zhí)行多個(gè) JavaScript 腳本,每個(gè)腳本執(zhí)行流都稱(chēng)為一個(gè)線程漓雅,彼此間互相獨(dú)立录别,并且有瀏覽器中的 JavaScript 引擎負(fù)責(zé)管理。下面我們將詳細(xì)講解 HTML5 的工作線程原理邻吞。

工作線程與多線程編程
在 HTML5 中组题,工作線程的出現(xiàn)使得在 Web 頁(yè)面中進(jìn)行多線程編程成為可能。眾所周知抱冷,傳統(tǒng)頁(yè)面中(HTML5 之前)的 JavaScript 的運(yùn)行都是以單線程的方式工作的崔列,雖然有多種方式實(shí)現(xiàn)了對(duì)多線程的模擬(例如:JavaScript 中的 setinterval 方法,setTimeout 方法等)旺遮,但是在本質(zhì)上程序的運(yùn)行仍然是由 JavaScript 引擎以單線程調(diào)度的方式進(jìn)行的赵讯。在 HTML5 中引入的工作線程使得瀏覽器端的 JavaScript 引擎可以并發(fā)地執(zhí)行 JavaScript 代碼,從而實(shí)現(xiàn)了對(duì)瀏覽器端多線程編程的良好支持耿眉。

HTML5 中的 Web Worker 可以分為兩種不同線程類(lèi)型边翼,一個(gè)是專(zhuān)用線程 Dedicated Worker,一個(gè)是共享線程 Shared Worker鸣剪。兩種類(lèi)型的線程各有不同的用途组底。下面對(duì)這兩種工作線程作了詳細(xì)的說(shuō)明和描述丈积。

專(zhuān)用線程:Dedicated Worker
專(zhuān)用線程(dedicated worker)的創(chuàng)建方式:

在創(chuàng)建專(zhuān)用線程的時(shí)候,需要給 Worker 的構(gòu)造函數(shù)提供一個(gè)指向 JavaScript 文件資源的 URL斤寇,這也是創(chuàng)建專(zhuān)用線程時(shí) Worker 構(gòu)造函數(shù)所需要的唯一參數(shù)桶癣。當(dāng)這個(gè)構(gòu)造函數(shù)被調(diào)用之后,一個(gè)工作線程的實(shí)例便會(huì)被創(chuàng)建出來(lái)娘锁。下面是創(chuàng)建專(zhuān)用線程代碼示例:

清單 1. 創(chuàng)建專(zhuān)用線程示例代碼
var worker = new Worker(‘dedicated.js’);
2牙寞、與一個(gè)專(zhuān)用線程通信:
專(zhuān)用線程在運(yùn)行的過(guò)程中會(huì)在后臺(tái)使用 MessagePort 對(duì)象,而 MessagePort 對(duì)象支持 HTML5 中多線程提供的所有功能莫秆,例如:可以發(fā)送和接受結(jié)構(gòu)化數(shù)據(jù)(JSON 等)间雀,傳輸二進(jìn)制數(shù)據(jù),并且支持在不同端口中傳輸數(shù)據(jù)等镊屎。
為了在頁(yè)面主程序接收從專(zhuān)用線程傳遞過(guò)來(lái)的消息惹挟,我們需要使用工作線程的 onmessage 事件處理器,定義 onmessage 的實(shí)例代碼如下:

清單 2. 接收來(lái)至工作線程示例代碼
worker.onmessage = function (event) { … }; 另外缝驳,開(kāi)發(fā)人員也可以選擇使用 addEventListener 方法连锯,它最終的實(shí)現(xiàn)方式和作用和 onmessage 相同。

就像前面講述的用狱,專(zhuān)用線程會(huì)使用隱式的 MessagePort 實(shí)例运怖,當(dāng)專(zhuān)用線程被創(chuàng)建的時(shí)候,MessagePort 的端口消息隊(duì)列便被主動(dòng)啟用夏伊。因此摇展,這也和工作線程接口中定義的 start 方法作用一致。

如果要想一個(gè)專(zhuān)用線程發(fā)送數(shù)據(jù)溺忧,那么我們需要使用線程中的 postMessage 方法咏连。專(zhuān)用線程不僅僅支持傳輸二進(jìn)制數(shù)據(jù),也支持結(jié)構(gòu)化的 JavaScript 數(shù)據(jù)格式鲁森。在這里有一點(diǎn)需要注意祟滴,為了高效地傳輸 ArrayBuffer 對(duì)象數(shù)據(jù),需要在 postMessage 方法中的第二個(gè)參數(shù)中指定它歌溉。實(shí)例代碼如下:

清單 3. 高效的發(fā)送 ArrayBuffer 數(shù)據(jù)代碼

worker.postMessage({
  operation: 'list_all_users',
  //ArrayBuffer object
  input: buffer,
  threshold: 0.8,
 }, [buffer]); //共享線程 Shared Worker共享線程

共享線程可以由兩種方式來(lái)定義:一是通過(guò)指向 JavaScript 腳本資源的 URL 來(lái)創(chuàng)建踱启,而是通過(guò)顯式的名稱(chēng)。當(dāng)由顯式的名稱(chēng)來(lái)定義的時(shí)候研底,由創(chuàng)建這個(gè)共享線程的第一個(gè)頁(yè)面中使用 URL 會(huì)被用來(lái)作為這個(gè)共享線程的 JavaScript 腳本資源 URL。通過(guò)這樣一種方式透罢,它允許同域中的多個(gè)應(yīng)用程序使用同一個(gè)提供公共服務(wù)的共享線程榜晦,從而不需要所有的應(yīng)用程序都去與這個(gè)提供公共服務(wù)的 URL 保持聯(lián)系。

無(wú)論在什么情況下羽圃,共享線程的作用域或者是生效范圍都是由創(chuàng)建它的域來(lái)定義的乾胶。因此,兩個(gè)不同的站點(diǎn)(即域)使用相同的共享線程名稱(chēng)也不會(huì)沖突。

共享線程的創(chuàng)建

創(chuàng)建共享線程可以通過(guò)使用 SharedWorker() 構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)识窿,這個(gè)構(gòu)造函數(shù)使用 URL 作為第一個(gè)參數(shù)斩郎,即是指向 JavaScript 資源文件的 URL,同時(shí)喻频,如果開(kāi)發(fā)人員提供了第二個(gè)構(gòu)造參數(shù)缩宜,那么這個(gè)參數(shù)將被用于作為這個(gè)共享線程的名稱(chēng)。創(chuàng)建共享線程的代碼示例如下:

var worker = new SharedWorker('sharedworker.js', ' mysharedworker ');//與共享線程通信

共享線程的通信也是跟專(zhuān)用線程一樣甥温,是通過(guò)使用隱式的 MessagePort 對(duì)象實(shí)例來(lái)完成的锻煌。當(dāng)使用 SharedWorker() 構(gòu)造函數(shù)的時(shí)候,這個(gè)對(duì)象將通過(guò)一種引用的方式被返回回來(lái)姻蚓。我們可以通過(guò)這個(gè)引用的 port 端口屬性來(lái)與它進(jìn)行通信宋梧。發(fā)送消息與接收消息的代碼示例如下:

清單 4. 發(fā)送消息與接收消息代碼

// 從端口接收數(shù)據(jù) , 包括文本數(shù)據(jù)以及結(jié)構(gòu)化數(shù)據(jù)
  worker.port.onmessage = function (event) { define your logic here... };
 // 向端口發(fā)送普通文本數(shù)據(jù)
worker.port.postMessage('put your message here … ');
 // 向端口發(fā)送結(jié)構(gòu)化數(shù)據(jù)
 worker.port.postMessage({ username: 'usertext'; live_city:
 ['data-one', 'data-two', 'data-three','data-four']}); 

上面示例代碼中,第一個(gè)我們使用 onmessage 事件處理器來(lái)接收消息狰挡,第二個(gè)使用 postMessage 來(lái)發(fā)送普通文本數(shù)據(jù)捂龄,第三個(gè)使用 postMessage 來(lái)發(fā)送結(jié)構(gòu)化的數(shù)據(jù),這里我們使用了 JSON 數(shù)據(jù)格式加叁。

工作線程事件處理模型
當(dāng)工作線程被一個(gè)具有 URL 參數(shù)的構(gòu)造函數(shù)創(chuàng)建的時(shí)候倦沧,它需要有一系列的處理流程來(lái)處理和記錄它本身的數(shù)據(jù)和狀態(tài)。下面我們給出了工作線程的處理模型如下(注:更多規(guī)范建議參考 W3C 中的最新規(guī)范):

創(chuàng)建一個(gè)獨(dú)立的并行處理環(huán)境殉农,并且在這個(gè)環(huán)境里面異步的運(yùn)行下面的步驟刀脏。
如果它的全局作用域是 SharedWorkerGlobalScope 對(duì)象,那么把最合適的應(yīng)用程序緩存和它聯(lián)系在一起超凳。
嘗試從它提供的 URL 里面使用 synchronous 標(biāo)志和 force same-origin 標(biāo)志獲取腳本資源愈污。

新腳本創(chuàng)建的時(shí)候會(huì)按照下面的步驟:

創(chuàng)建這個(gè)腳本的執(zhí)行環(huán)境。
使用腳本的執(zhí)行環(huán)境解析腳本資源轮傍。
設(shè)置腳本的全局變量為工作線程全局變量暂雹。
設(shè)置腳本編碼為 UTF-8 編碼。

啟動(dòng)線程監(jiān)視器创夜,關(guān)閉孤兒線程杭跪。

對(duì)于掛起線程,啟動(dòng)線程監(jiān)視器監(jiān)視掛起線程的狀態(tài)驰吓,即時(shí)在并行環(huán)境中更改它們的狀態(tài)涧尿。
跳入腳本初始點(diǎn),并且啟動(dòng)運(yùn)行檬贰。
如果其全局變量為 DedicatedWorkerGlobalScope 對(duì)象姑廉,然后在線程的隱式端口中啟用端口消息隊(duì)列。
對(duì)于事件循環(huán)翁涤,等待一直到事件循環(huán)列表中出現(xiàn)新的任務(wù)桥言。
首先運(yùn)行事件循環(huán)列表中的最先進(jìn)入的任務(wù)萌踱,但是用戶代理可以選擇運(yùn)行任何一個(gè)任務(wù)。
如果事件循環(huán)列表?yè)碛写鎯?chǔ) mutex 互斥信號(hào)量号阿,那么釋放它并鸵。
當(dāng)運(yùn)行完一個(gè)任務(wù)后,從事件循環(huán)列表中刪除它扔涧。
如果事件循環(huán)列表中還有任務(wù)园担,那么繼續(xù)前面的步驟執(zhí)行這些任務(wù)。
如果活動(dòng)超時(shí)后扰柠,清空工作線程的全局作用域列表粉铐。
釋放工作線程的端口列表中的所有端口。
工作線程應(yīng)用范圍和作用域
工作線程的全局作用域僅僅限于工作線程本身卤档,即在線程的生命周期內(nèi)有效蝙泼。規(guī)范中 WorkerGlobalScope 接口代表了它的全局作用域,下面我們來(lái)看下這個(gè)接口的具體實(shí)施細(xì)節(jié)(WorkerGlobalScope 抽象接口)劝枣。

清單 5. WorkerGlobalScope 抽象接口代碼

 interface WorkerGlobalScope {
  readonly attribute WorkerGlobalScope self;
  readonly attribute WorkerLocation location;

  void close();
           attribute Function onerror;
 };
 WorkerGlobalScope implements WorkerUtils;
 WorkerGlobalScope implements EventTarget;  

我們可以使用 WorkerGlobalScope 的 self 屬性來(lái)或者這個(gè)對(duì)象本身的引用汤踏。location 屬性返回當(dāng)線程被創(chuàng)建出來(lái)的時(shí)候與之關(guān)聯(lián)的 WorkerLocation 對(duì)象,它表示用于初始化這個(gè)工作線程的腳步資源的絕對(duì) URL舔腾,即使頁(yè)面被多次重定向后溪胶,這個(gè) URL 資源位置也不會(huì)改變。

當(dāng)腳本調(diào)用 WorkerGlobalScope 上的 close()方法后稳诚,會(huì)自動(dòng)的執(zhí)行下面的兩個(gè)步驟:

刪除這個(gè)工作線程事件隊(duì)列中的所有任務(wù)哗脖。
設(shè)置 WorkerGlobalScope 對(duì)象的 closing 狀態(tài)為 true (這將阻止以后任何新的任務(wù)繼續(xù)添加到事件隊(duì)列中來(lái))。
工作線程生命周期
工作線程之間的通信必須依賴(lài)于瀏覽器的上下文環(huán)境扳还,并且通過(guò)它們的 MessagePort 對(duì)象實(shí)例傳遞消息才避。每個(gè)工作線程的全局作用域都擁有這些線程的端口列表,這些列表包括了所有線程使用到的 MessagePort 對(duì)象氨距。在專(zhuān)用線程的情況下桑逝,這個(gè)列表還會(huì)包含隱式的 MessagePort 對(duì)象。

每個(gè)工作線程的全局作用域?qū)ο?WorkerGlobalScope 還會(huì)有一個(gè)工作線程的線程列表俏让,在初始化時(shí)這個(gè)列表為空楞遏。當(dāng)工作線程被創(chuàng)建的時(shí)候或者擁有父工作線程的時(shí)候,它們就會(huì)被填充進(jìn)來(lái)首昔。

最后寡喝,每個(gè)工作線程的全局作用域?qū)ο?WorkerGlobalScope 還擁有這個(gè)線程的文檔模型,在初始化時(shí)這個(gè)列表為空勒奇。當(dāng)工作線程被創(chuàng)建的時(shí)候拘荡,文檔對(duì)象就會(huì)被填充進(jìn)來(lái)。無(wú)論何時(shí)當(dāng)一個(gè)文檔對(duì)象被丟棄的時(shí)候撬陵,它就要從這個(gè)文檔對(duì)象列舉里面刪除出來(lái)珊皿。

在工作線程的生命周期中,定義了下面四種不同類(lèi)型的線程名稱(chēng)巨税,用以標(biāo)識(shí)它們?cè)诰€程的整個(gè)生命周期中的不同狀態(tài):

當(dāng)一個(gè)工作線程的文檔對(duì)象列舉不為空的時(shí)候蟋定,這個(gè)工作線程會(huì)被稱(chēng)之為許可線程。(A worker is said to be a permissible worker if its list of the worker’s Documents is not empty.)

當(dāng)一個(gè)工作線程是許可線程并且或者擁有數(shù)據(jù)庫(kù)事務(wù)或者擁有網(wǎng)絡(luò)連接或者它的工作線程列表不為空的時(shí)候草添,這個(gè)工作線程會(huì)被稱(chēng)之為受保護(hù)的線程驶兜。(A worker is said to be a protected worker if it is a permissible worker and either it has outstanding timers, database transactions, or network connections, or its list of the worker’s ports is not empty)

當(dāng)一個(gè)工作線程的文檔對(duì)象列表中的任何一個(gè)對(duì)象都是處于完全活動(dòng)狀態(tài)的時(shí)候,這個(gè)工作線程會(huì)被稱(chēng)之為需要激活線程远寸。(A worker is said to be an active needed worker if any of the Document objects in the worker’s Documents are fully active.)

當(dāng)一個(gè)工作線程是一個(gè)非需要激活線程同時(shí)又是一個(gè)許可線程的時(shí)候抄淑,這個(gè)工作線程會(huì)被稱(chēng)之為掛起線程。(A worker is said to be a suspendable worker if it is not an active needed worker but it is a permissible worker.)

工作線程(Web Worker)API 接口
類(lèi)庫(kù)和腳本的訪問(wèn)和引入

對(duì)于類(lèi)庫(kù)和腳本的訪問(wèn)和引入驰后,規(guī)范中規(guī)定可以使用 WorkerGlobalScope 對(duì)象的 importScripts(urls) 方法來(lái)引入網(wǎng)絡(luò)中的腳本資源肆资。當(dāng)用戶調(diào)用這個(gè)方法引入資源的時(shí)候會(huì)執(zhí)行下面的步驟來(lái)完成這個(gè)操作:

如果沒(méi)有給 importScripts 方法任何參數(shù),那么立即返回灶芝,終止下面的步驟郑原。

解析 importScripts 方法的每一個(gè)參數(shù)。

如果有任何失敗或者錯(cuò)誤夜涕,拋出 SYNTAX_ERR 異常犯犁。

嘗試從用戶提供的 URL 資源位置處獲取腳本資源。

對(duì)于 importScripts 方法的每一個(gè)參數(shù)女器,按照用戶的提供順序酸役,獲取腳本資源后繼續(xù)進(jìn)行其它操作。

清單 6. 外部資源腳本引入和訪問(wèn)示例代碼

 /**
 * 使用 importScripts 方法引入外部資源腳本驾胆,在這里我們使用了數(shù)學(xué)公式計(jì)算工具庫(kù) math_utilities.js
 * 當(dāng) JavaScript 引擎對(duì)這個(gè)資源文件加載完畢后涣澡,繼續(xù)執(zhí)行下面的代碼。同時(shí)俏拱,下面的的代碼可以訪問(wèn)和調(diào)用
 * 在資源文件中定義的變量和方法暑塑。
 **/
 importScripts('math_utilities.js');

 /**
 * This worker is used to calculate
 * the least common multiple
 * and the greatest common divisor
 */
 onmessage = function (event)
 {
 var first=event.data.first;
 var second=event.data.second;
 calculate(first,second);
 };


 /*
 * calculate the least common multiple
 * and the greatest common divisor
 */
 function calculate(first,second) {
    //do the calculation work
 var common_divisor=divisor(first,second);
 var common_multiple=multiple(first,second);
    postMessage("Work done! " +
"The least common multiple is "+common_divisor
 +" and the greatest common divisor is "+common_multiple);
 }

工作導(dǎo)航器對(duì)象(WorkerNavigator)
在 HTML5 中, WorkerUtils 接口的 navigator 屬性會(huì)返回一個(gè)工作導(dǎo)航器對(duì)象(WorkerNavigator)锅必,這個(gè)對(duì)象定義并且代表了用戶代理(即 Web 客戶端)的標(biāo)識(shí)和狀態(tài)事格。因此,用戶和 Web 腳本開(kāi)發(fā)人員可以在多線程開(kāi)發(fā)過(guò)程中通過(guò)這個(gè)對(duì)象來(lái)取得或者確定用戶的狀態(tài)搞隐。

WorkerUtils 抽象接口的 navigator 屬性會(huì)返回一個(gè) WorkerNavigator 用戶接口驹愚,用于用戶代理的識(shí)別的狀態(tài)標(biāo)識(shí)。我們來(lái)看下 WorkerNavigator 接口的定義劣纲。

WorkerNavigator 接口定義

清單 7. WorkerNavigator 接口定義代碼

interface WorkerNavigator {};  WorkerNavigator implements NavigatorID;  WorkerNavigator implements NavigatorOnLine; 

其中逢捺,有一點(diǎn)需要注意:如果接口的相對(duì)命名空間對(duì)象為 Window 對(duì)象的時(shí)候,WorkerNavigator 對(duì)象一定不可以存在癞季,即無(wú)法再使用這個(gè)對(duì)象劫瞳。

創(chuàng)建與終止線程
在講解創(chuàng)建新的工作線程之前倘潜,我們先看下 W3C 規(guī)范對(duì)工作線程的定義。工作線程規(guī)范中定義了線程的抽象接口類(lèi) AbstractWorker 志于,專(zhuān)用線程以及共享線程都繼承自該抽象接口涮因。專(zhuān)用線程以及共享線程的創(chuàng)建方法讀者可以參考第一小節(jié)中的示例代碼。下面是此抽象接口的定義伺绽。

AbstractWorker 抽象接口
清單 8. AbstractWorker 抽象接口代碼

 [Supplemental, NoInterfaceObject]
 interface AbstractWorker { attribute Function onerror; };
 AbstractWorker implements EventTarget;

此外养泡,該接口還定義了錯(cuò)誤處理的事件處理器 onerror,當(dāng)工作線程在通信過(guò)程中遇到錯(cuò)誤時(shí)便會(huì)觸發(fā)這個(gè)事件處理器奈应。

專(zhuān)用線程及其定義
清單 9. 專(zhuān)用線程定義代碼

 [Constructor(in DOMString scriptURL)]
 interface Worker : AbstractWorker {
  void terminate();

 void postMessage(in any message, in optional MessagePortArray ports);
     attribute Function onmessage;
 };

當(dāng)創(chuàng)建完線程以后澜掩,我們可以調(diào)用 terminate() 方法去終止一個(gè)線程。每個(gè)專(zhuān)用線程都擁有一個(gè)隱式的 MessagePort 對(duì)象與之相關(guān)聯(lián)杖挣。這個(gè)端口隨著線程的創(chuàng)建而被創(chuàng)建出來(lái)肩榕,但并沒(méi)有暴露給用戶。所有的基于這個(gè)端口的消息接收都以線程本身為目標(biāo)程梦。

共享線程及其定義
清單 10. 共享線程定義代碼

 [Constructor(DOMString scriptURL, optional DOMString name)]
 interface SharedWorker : AbstractWorker {
 readonly attribute MessagePort port;
 };

共享線程同專(zhuān)用線程一樣点把,當(dāng)創(chuàng)建完線程以后,我們可以調(diào)用 terminate() 方法去終止一個(gè)共享線程屿附。

工作線程位置屬性

工作線程被創(chuàng)建出來(lái)以后郎逃,需要記錄它的狀態(tài)以及位置信息,在工作線程規(guī)范中定義了 WorkerLocation 來(lái)表示它們的位置挺份。接口定義如下:

清單 11. 共享線程定義代碼

 interface WorkerLocation {
  // URL decomposition IDL attributes
  stringifier readonly attribute DOMString href;
  readonly attribute DOMString protocol;
  readonly attribute DOMString host;
  readonly attribute DOMString hostname;
  readonly attribute DOMString port;
  readonly attribute DOMString pathname;
  readonly attribute DOMString search;
  readonly attribute DOMString hash;
 };

WorkerLocation 對(duì)象表示了工作線程腳本資源的絕對(duì) URL 信息褒翰。我們可以使用它的 href 屬性取得這個(gè)對(duì)象的絕對(duì) URL。WorkerLocation 接口還定義了與位置信息有關(guān)的其它屬性匀泊,例如:用于信息傳輸?shù)膮f(xié)議(protocol)优训,主機(jī)名稱(chēng)(hostname),端口(port)各聘,路徑名稱(chēng)(pathname)等揣非。

工作線程(Web Worker)應(yīng)用與實(shí)踐
我們可以寫(xiě)出很多的例子來(lái)說(shuō)明后臺(tái)工作線程的合適的用法,下面我們以幾種典型的應(yīng)用場(chǎng)景為例躲因,用代碼實(shí)例的形式講解在各種需求背景下正確的使用它們早敬。

應(yīng)用場(chǎng)景一:使用工作線程做后臺(tái)數(shù)值(算法)計(jì)算

工作線程最簡(jiǎn)單的應(yīng)用就是用來(lái)做后臺(tái)計(jì)算,而這種計(jì)算并不會(huì)中斷前臺(tái)用戶的操作大脉。下面我們提供了一個(gè)工作線程的代碼片段搞监,用來(lái)執(zhí)行一個(gè)相對(duì)來(lái)說(shuō)比較復(fù)雜的任務(wù):計(jì)算兩個(gè)非常大的數(shù)字的最小公倍數(shù)和最大公約數(shù)。

在這個(gè)例子中镰矿,我們?cè)谥黜?yè)面中創(chuàng)建一個(gè)后臺(tái)工作線程琐驴,并且向這個(gè)工作線程分配任務(wù)(即傳遞兩個(gè)特別大的數(shù)字),當(dāng)工作線程執(zhí)行完這個(gè)任務(wù)時(shí),便向主頁(yè)面程序返回計(jì)算結(jié)果绝淡,而在這個(gè)過(guò)程中宙刘,主頁(yè)面不需要等待這個(gè)耗時(shí)的操作,可以繼續(xù)進(jìn)行其它的行為或任務(wù)牢酵。

我們把這個(gè)應(yīng)用場(chǎng)景分為兩個(gè)主要部分荐类,一個(gè)是主頁(yè)面,可以包含主 JavaScript 應(yīng)用入口茁帽,用戶其它操作 UI 等。另外一個(gè)是后臺(tái)工作線程腳本屈嗤,即用來(lái)執(zhí)行計(jì)算任務(wù)潘拨。代碼片段如下:

清單 12. 主程序頁(yè)面代碼

 <!DOCTYPE HTML>
 <html>
 <head>
 <title>
 Background Worker Application Example 1: Complicated Number Computation
 </title>
 </head>
 <body>
 <div>
 The least common multiple and greatest common divisor is:
 <p id="computation_results">please wait, computing … </p>
 </div>
  <script>
   var worker = new Worker('numberworker.js');
 worker.postMessage("{first:347734080,second:3423744400}");
   worker.onmessage = function (event)
 {
  document.getElementById(' computation_result').textContent = event.data;
 };
  </script>
 </body>
 </html> 清單 13. 后臺(tái)工作線程代碼

 /**
 * This worker is used to calculate
 * the least common multiple
 * and the greatest common divisor
 */

 onmessage = function (event)
 {
 var first=event.data.first;
 var second=event.data.second;
 calculate(first,second);
 };


 /*
 * calculate the least common multiple
 * and the greatest common divisor
 */
 function calculate(first,second) {
    //do the calculation work
 var common_divisor=divisor(first,second);
 var common_multiple=multiple(first,second);
    postMessage("Work done! " +
"The least common multiple is "+common_divisor
 +" and the greatest common divisor is "+common_multiple);
 }



 /**
 * calculate the greatest common divisor
 * @param number
 * @param number
 * @return
 */
 function divisor(a, b) {
 if (a % b == 0) {
 return b;
 } else {
 return divisor(b, a % b);
 }
 }

 /**
 * calculate the least common multiple
 * @param number
 * @param number
 * @return
 */
 function multiple( a,  b) {
 var multiple = 0;
 multiple = a * b / divisor(a, b);
 return multiple;
 } 

在主程序頁(yè)面中,我們使用 Worker()構(gòu)造函數(shù)創(chuàng)建一個(gè)新的工作線程饶号,它會(huì)返回一個(gè)代表此線程本身的線程對(duì)象铁追。接下來(lái)我們使用這個(gè)線程對(duì)象與后臺(tái)腳本進(jìn)行通信。線程對(duì)象有兩個(gè)主要事件處理器:postMessage 和 onmessage 茫船。postMessage 用來(lái)向后臺(tái)腳本發(fā)送消息琅束,onmessage 用以接收從后臺(tái)腳本中傳遞過(guò)來(lái)的消息。

在后臺(tái)工作線程代碼片段中算谈,我們定一個(gè)兩個(gè) JavaScript 函數(shù)涩禀,一個(gè)是 function divisor:用以計(jì)算最大公約數(shù),一個(gè)是 function multiple:用以計(jì)算最小公倍數(shù)然眼。同時(shí)工作線程的 onmessage 事件處理器用以接收從主頁(yè)面中傳遞過(guò)來(lái)的數(shù)值艾船,然后把這兩個(gè)數(shù)值傳遞到 function calculate 用以計(jì)算。當(dāng)計(jì)算完成后高每,調(diào)用事件處理器 postMessage屿岂,把計(jì)算結(jié)果發(fā)送到主頁(yè)面。

應(yīng)用場(chǎng)景二:使用共享線程處理多用戶并發(fā)連接

由于線程的構(gòu)建以及銷(xiāo)毀都要消耗很多的系統(tǒng)性能鲸匿,例如 CPU 的處理器調(diào)度爷怀,內(nèi)存的占用回收等,在一般的編程語(yǔ)言中都會(huì)有線程池的概念带欢,線程池是一種對(duì)多線程并發(fā)處理的形式运授,在處理過(guò)程中系統(tǒng)將所有的任務(wù)添加到一個(gè)任務(wù)隊(duì)列,然后在構(gòu)建好線程池以后自動(dòng)啟動(dòng)這些任務(wù)洪囤。處理完任務(wù)后再把線程收回到線程池中徒坡,用于下一次任務(wù)調(diào)用。線程池也是共享線程的一種應(yīng)用瘤缩。

在 HTML5 中也引入了共享線程技術(shù)喇完,但是由于每個(gè)共享線程可以有多個(gè)連接,HTML5 對(duì)共享線程提供了和普通工作線程稍微有些區(qū)別的 API 接口剥啤。下面我們提供幾個(gè)例子來(lái)講述對(duì)共享線程的用法锦溪。

下面我們給出一個(gè)例子:創(chuàng)建一個(gè)共享線程用于接收從不同連接發(fā)送過(guò)來(lái)的指令不脯,然后實(shí)現(xiàn)自己的指令處理邏輯,指令處理完成后將結(jié)果返回到各個(gè)不同的連接用戶刻诊。

清單 14. 共享線程用戶連接頁(yè)面代碼

<!DOCTYPE html>
 <html>
 <head>
 <meta charset="UTF-8">
 <title>Shared worker example: how to use shared worker in HTML5</title>

 <script>
  var worker = new SharedWorker('sharedworker.js');
  var log = document.getElementById('response_from_worker');
  worker.port.addEventListener('message', function(e) {
 //log the response data in web page
 log.textContent =e.data;
  }, false);
  worker.port.start();
  worker.port.postMessage('ping from user web page..');

  //following method will send user input to sharedworker
  function postMessageToSharedWorker(input)
  {
  //define a json object to construct the request
  var instructions={instruction:input.value};
  worker.port.postMessage(instructions);
  }
 </script>

 </head>
 <body onload=''>
 <output id='response_from_worker'>
 Shared worker example: how to use shared worker in HTML5
 </output>
 send instructions to shared worker:
 <input type="text" autofocus oninput="postMessageToSharedWorker(this);return false;">
 </input>
 </body>
 </html>

清單 15. 用于處理用戶指令的共享線程代碼

// 創(chuàng)建一個(gè)共享線程用于接收從不同連接發(fā)送過(guò)來(lái)的指令防楷,指令處理完成后將結(jié)果返回到各個(gè)不同的連接用戶. 
/* define a connect count to trace connecting 
* this variable will be shared within all connections 
*/ 
var connect_number = 0; 
onconnect = function(e) { 
connect_number =connect_number+ 1; 
//get the first port here 
var port = e.ports[0]; 
port.postMessage('A new connection! The current connection number is ' 
+ connect_number); 
port.onmessage = function(e) { 
//get instructions from requester 
var instruction=e.data.instruction; 
var results=execute_instruction(instruction); 
port.postMessage('Request: '+instruction+' Response '+results 
+' from shared worker...'); 
}; 
}; 
/* this function will be used to execute the instructions send from requester 
* @param instruction 
* @return 
*/ 
function execute_instruction(instruction){ 
var result_value; 
//implement your logic here 
//execute the instruction... 
return result_value 
} 

在上面的共享線程例子中,在主頁(yè)面即各個(gè)用戶連接頁(yè)面構(gòu)造出一個(gè)共享線程對(duì)象则涯,然后定義了一個(gè)方法 postMessageToSharedWorker 向共享線程發(fā)送來(lái)之用戶的指令复局。同時(shí),在共享線程的實(shí)現(xiàn)代碼片段中定義 connect_number 用來(lái)記錄連接到這個(gè)共享線程的總數(shù)粟判。之后亿昏,用 onconnect 事件處理器接受來(lái)自不同用戶的連接,解析它們傳遞過(guò)來(lái)的指令档礁。最后角钩,定義一個(gè)了方法 execute_instruction 用于執(zhí)行用戶的指令,指令執(zhí)行完成后將結(jié)果返回給各個(gè)用戶呻澜。

這里我們并沒(méi)有跟前面的例子一樣使用到了工作線程的 onmessage 事件處理器递礼,而是使用了另外一種方式 addEventListener。實(shí)際上羹幸,這兩種的實(shí)現(xiàn)原理基本一致脊髓,只有有些稍微的差別,如果使用到了 addEventListener 來(lái)接受來(lái)自共享線程的消息睹欲,那么就要使用 worker.port.start() 方法來(lái)啟動(dòng)這個(gè)端口供炼。之后就可以像工作線程的使用方式一樣正常的接收和發(fā)送消息。

應(yīng)用場(chǎng)景三:HTML5 線程代理

多線程代理技術(shù)

隨著多核處理器的流行窘疮,現(xiàn)代的計(jì)算機(jī)一般都擁有多核的 CPU袋哼,這也使得任務(wù)能夠在處理器級(jí)別上并發(fā)執(zhí)行。如果我們要在一個(gè)具有多核 CPU 的客戶端上用單線程去執(zhí)行程序即處理業(yè)務(wù)邏輯闸衫,往往不能最大化的利用系統(tǒng)資源涛贯。因此,在這種情況下我們可以將一個(gè)耗時(shí)或者復(fù)雜的任務(wù)拆分成多個(gè)子任務(wù)蔚出,把每一個(gè)子任務(wù)分擔(dān)給一個(gè)工作線程弟翘,這樣多個(gè)工作現(xiàn)場(chǎng)就共同承擔(dān)了單個(gè)線程的工作負(fù)載,同時(shí)又能夠并發(fā)的去執(zhí)行骄酗,最大化的利用了系統(tǒng)資源(CPU稀余、內(nèi)存、I/O 等)趋翻。

下面我們向讀者提供一個(gè)線程代理應(yīng)用的例子:計(jì)算全球人口的數(shù)量睛琳。2

清單 16. 主頁(yè)面(僅僅是用來(lái)顯示計(jì)算結(jié)果)代碼

 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="UTF-8">
 <title>Shared worker example: how to use delegation worker in HTML5</title>

 <script>
  var worker = new SharedWorker('delegationworker.js');
  var log = document.getElementById('response_from_worker');
  worker.onmessage = function (event) {
  //resolve the population from delegation worker
  var resultdata=event.data;
  var population=resultdata.total_population;
  var showtext='The total population of the word is '+population;
  document.getElementById('response_from_worker').textContent = showtext;
  };
 </script>

 </head>
 <body onload=''>
 <output id='response_from_worker'>
 Shared worker example: how to use delegation worker in HTML5
 </output>
 </body>
 </html> 清單 17. 主工作線程代碼

 /*
 * define the country list in the whole word
 * take following Array as an example
 */
 var country_list = ['Albania','Algeria','American','Andorra','Angola','Antigua','....'];

 // define the variable to record the population of the word
 var total_population=0;
 var country_size=country_list.length;
 var processing_size=country_list.length;

 for (var i = 0; i < country_size; i++)
 {
  var worker = new Worker('subworker.js');
  //wrap the command, send to delegations
  var command={command:'start',country:country_list[i]};
  worker.postMessage(command);
  worker.onmessage = update_results;
 }

 /*
 * this function will be used to update the result
 * @param event
 * @return
 */

 function storeResult(event)
 {
 total_population += event.data;
 processing_size -= 1;
 if (processing_size <= 0)
 {
 //complete the whole work, post results to web page
 postMessage(total_population);
 }
 }
 清單 18. 代理線程代碼

 //define the onmessage hander for the delegation
 onmessage = start_calculate;

 /*
 * resolve the command and kick off the calculation
 */
 function start_calculate(event)
 {
 var command=event.data.command;
 if(command!=null&&command=='start')
 {
 var coutry=event.data.country;
 do_calculate(country);
 }
 onmessage = null;
 }

 /*
 * the complex calculation method defined here
 * return the population of the country
 */
 function do_calculate(country)
 {
  var population = 0;
  var cities=//get all the cities for this country
  for (var i = 0; i < cities.length; i++)
  {
  var city_popu=0;
      // perform the calculation for this city
  //update the city_popu
  population += city_popu;
  }
  postMessage(population);
  close();
 }

總結(jié)
HTML5 Web Worker 的多線程特性為基于 Web 系統(tǒng)開(kāi)發(fā)的程序人員提供了強(qiáng)大的并發(fā)程序設(shè)計(jì)功能,它允許開(kāi)發(fā)人員設(shè)計(jì)開(kāi)發(fā)出性能和交互更好的富客戶端應(yīng)用程序。本文不僅僅詳細(xì)講述 HTML5 中的多線程規(guī)范师骗。同時(shí)历等,也以幾種典型的應(yīng)用場(chǎng)景為例,以實(shí)例的形式講解 HTML5 中多線程編程以及應(yīng)用辟癌,為用戶提供了詳細(xì)而全面的參考價(jià)值寒屯,并且指導(dǎo)開(kāi)發(fā)人員設(shè)計(jì)和構(gòu)建更為高效和穩(wěn)定的 Web 多線程應(yīng)用。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末黍少,一起剝皮案震驚了整個(gè)濱河市寡夹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌厂置,老刑警劉巖要出,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異农渊,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)或颊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)砸紊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人囱挑,你說(shuō)我怎么就攤上這事醉顽。” “怎么了平挑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵游添,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我通熄,道長(zhǎng)唆涝,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任唇辨,我火速辦了婚禮安拟,結(jié)果婚禮上傀缩,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好泼疑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著誉己,像睡著了一般侨舆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栗恩,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天透乾,我揣著相機(jī)與錄音,去河邊找鬼。 笑死续徽,一個(gè)胖子當(dāng)著我的面吹牛蚓曼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钦扭,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纫版,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了客情?” 一聲冷哼從身側(cè)響起其弊,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膀斋,沒(méi)想到半個(gè)月后梭伐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仰担,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年糊识,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摔蓝。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赂苗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贮尉,到底是詐尸還是另有隱情拌滋,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布猜谚,位于F島的核電站败砂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏魏铅。R本人自食惡果不足惜昌犹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望览芳。 院中可真熱鬧祭隔,春花似錦、人聲如沸路操。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)屯仗。三九已至搞坝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間魁袜,已是汗流浹背桩撮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工敦第, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人店量。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓芜果,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親融师。 傳聞我的和親對(duì)象是個(gè)殘疾皇子右钾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • His songs are sooo great to listen to. Some Might be owed...
    Piggy_Ann閱讀 187評(píng)論 0 0
  • 等待, 蹉跎了歲月旱爆, 荒蕪了青春舀射, 埋沒(méi)了夢(mèng)想, 錯(cuò)過(guò)了太多的美好怀伦。 給我們希望卻又看不到遠(yuǎn)方脆烟, 讓人失望卻又不至...
    velove閱讀 176評(píng)論 0 0
  • 我們期盼已久的小橋詩(shī)會(huì)開(kāi)始了,我又開(kāi)心又緊張房待。我們上午上課邢羔,過(guò)了一會(huì)就去練習(xí)臺(tái)詞,很多同學(xué)都不熟練桑孩,還是要練習(xí)一下...
    禹晴閱讀 110評(píng)論 0 0
  • 不知道為什么张抄,聽(tīng)到這首歌我總有要哭的感覺(jué)⊥菡或許是歌詞中的深情,亦或者是為了那個(gè)曾經(jīng)來(lái)看我的他左驾,那個(gè)我想要去看的他镣隶。...
    程秋誼閱讀 209評(píng)論 0 0
  • 今天上午,終于考完試了诡右。 “呃啊安岂,終于寫(xiě)完了》牵”我撐著懶腰說(shuō)域那,剛說(shuō)完,我又開(kāi)始認(rèn)真的檢查猜煮。真希望...
    時(shí)綺閱讀 177評(píng)論 0 3