轉(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)用。