深入HTML5應(yīng)用實(shí)踐:多線程編程
HTML5 中工作線程(Web Worker)簡介
至 2008 年 W3C 制定出第一個(gè) HTML5 草案開始,HTML5 承載了越來越多嶄新的特性和功能。它不但強(qiáng)化了 Web 系統(tǒng)或網(wǎng)頁的表現(xiàn)性能,而且還增加了對本地?cái)?shù)據(jù)庫等 Web 應(yīng)用功能的支持。其中,最重要的一個(gè)便是對多線程的支持。在 HTML5 中提出了工作線程(Web Worker)的概念,并且規(guī)范出 Web Worker 的三大主要特征:能夠長時(shí)間運(yùn)行(響應(yīng)),理想的啟動(dòng)性能以及理想的內(nèi)存消耗。Web Worker 允許開發(fā)人員編寫能夠長時(shí)間運(yùn)行而不被用戶所中斷的后臺程序,去執(zhí)行事務(wù)或者邏輯,并同時(shí)保證頁面對用戶的及時(shí)響應(yīng)。本文深入 HTML5 多線程規(guī)范,講述多線程實(shí)現(xiàn)原理、方法,同時(shí)以實(shí)例的形式講解 HTML5 中多線程編程以及應(yīng)用。
W3C 中的工作線程規(guī)范到目前為止已經(jīng)定義了出了一系列公共接口,它允許 Web 程序開發(fā)人員去創(chuàng)建后臺線程在他們的主頁面中并發(fā)的運(yùn)行腳本。這將使得線程級別的消息通信成為現(xiàn)實(shí)。
詳解 HTML5 工作線程原理
傳統(tǒng)上的線程可以解釋為輕量級進(jìn)程,它和進(jìn)程一樣擁有獨(dú)立的執(zhí)行控制,一般情況下由操作系統(tǒng)負(fù)責(zé)調(diào)度。而在 HTML5 中的多線程是這樣一種機(jī)制,它允許在 Web 程序中并發(fā)執(zhí)行多個(gè) JavaScript 腳本,每個(gè)腳本執(zhí)行流都稱為一個(gè)線程,彼此間互相獨(dú)立,并且有瀏覽器中的 JavaScript 引擎負(fù)責(zé)管理。下面我們將詳細(xì)講解 HTML5 的工作線程原理。
工作線程與多線程編程
在 HTML5 中,工作線程的出現(xiàn)使得在 Web 頁面中進(jìn)行多線程編程成為可能。眾所周知,傳統(tǒng)頁面中(HTML5 之前)的 JavaScript 的運(yùn)行都是以單線程的方式工作的,雖然有多種方式實(shí)現(xiàn)了對多線程的模擬(例如:JavaScript 中的 setinterval 方法,setTimeout 方法等),但是在本質(zhì)上程序的運(yùn)行仍然是由 JavaScript 引擎以單線程調(diào)度的方式進(jìn)行的。在 HTML5 中引入的工作線程使得瀏覽器端的 JavaScript 引擎可以并發(fā)地執(zhí)行 JavaScript 代碼,從而實(shí)現(xiàn)了對瀏覽器端多線程編程的良好支持。
HTML5 中的 Web Worker 可以分為兩種不同線程類型,一個(gè)是專用線程 Dedicated Worker,一個(gè)是共享線程 Shared Worker。兩種類型的線程各有不同的用途。下面對這兩種工作線程作了詳細(xì)的說明和描述。
專用線程:Dedicated Worker
1. 專用線程(dedicated worker)的創(chuàng)建方式:
在創(chuàng)建專用線程的時(shí)候,需要給 Worker 的構(gòu)造函數(shù)提供一個(gè)指向 JavaScript 文件資源的 URL,這也是創(chuàng)建專用線程時(shí) Worker 構(gòu)造函數(shù)所需要的唯一參數(shù)。當(dāng)這個(gè)構(gòu)造函數(shù)被調(diào)用之后,一個(gè)工作線程的實(shí)例便會(huì)被創(chuàng)建出來。下面是創(chuàng)建專用線程代碼示例:
- 清單 1. 創(chuàng)建專用線程示例代碼
- var worker = new Worker('dedicated.js');
2. 與一個(gè)專用線程通信:
專用線程在運(yùn)行的過程中會(huì)在后臺使用 MessagePort 對象,而 MessagePort 對象支持 HTML5 中多線程提供的所有功能,例如:可以發(fā)送和接受結(jié)構(gòu)化數(shù)據(jù)(JSON 等),傳輸二進(jìn)制數(shù)據(jù),并且支持在不同端口中傳輸數(shù)據(jù)等。
為了在頁面主程序接收從專用線程傳遞過來的消息,我們需要使用工作線程的 onmessage 事件處理器,定義 onmessage 的實(shí)例代碼如下:
- 清單 2. 接收來至工作線程示例代碼
- worker.onmessage = function (event) { ... };
3.另外,開發(fā)人員也可以選擇使用 addEventListener 方法,它最終的實(shí)現(xiàn)方式和作用和 onmessage 相同。
就像前面講述的,專用線程會(huì)使用隱式的 MessagePort 實(shí)例,當(dāng)專用線程被創(chuàng)建的時(shí)候,MessagePort 的端口消息隊(duì)列便被主動(dòng)啟用。因此,這也和工作線程接口中定義的 start 方法作用一 致。
如果要想一個(gè)專用線程發(fā)送數(shù)據(jù),那么我們需要使用線程中的 postMessage 方法。專用線程不僅僅支持傳輸二進(jìn)制數(shù)據(jù),也支持結(jié)構(gòu)化的 JavaScript 數(shù)據(jù)格式。在這里有一點(diǎn)需要注意,為了高效地傳輸 ArrayBuffer 對象數(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
1.共享線程
共享線程可以由兩種方式來定義:一是通過指向 JavaScript 腳本資源的 URL 來創(chuàng)建,而是通過顯式的名稱。當(dāng)由顯式的名稱來定義的時(shí)候,由創(chuàng)建這個(gè)共享線程的第一個(gè)頁面中使用 URL 會(huì)被用來作為這個(gè)共享線程的 JavaScript 腳本資源 URL。通過這樣一種方式,它允許同域中的多個(gè)應(yīng)用程序使用同一個(gè)提供公共服務(wù)的共享線程,從而不需要所有的應(yīng)用程序都去與這個(gè)提供公共服務(wù)的 URL 保持聯(lián)系。
無論在什么情況下,共享線程的作用域或者是生效范圍都是由創(chuàng)建它的域來定義的。因此,兩個(gè)不同的站點(diǎn)(即域)使用相同的共享線程名稱也不會(huì)沖突。
2.共享線程的創(chuàng)建
創(chuàng)建共享線程可以通過使用 SharedWorker() 構(gòu)造函數(shù)來實(shí)現(xiàn),這個(gè)構(gòu)造函數(shù)使用 URL 作為第一個(gè)參數(shù),即是指向 JavaScript 資源文件的 URL,同時(shí),如果開發(fā)人員提供了第二個(gè)構(gòu)造參數(shù),那么這個(gè)參數(shù)將被用于作為這個(gè)共享線程的名稱。創(chuàng)建共享線程的代碼示例如下:
- / 從端口接收數(shù)據(jù) , 包括文本數(shù)據(jù)以及結(jié)構(gòu)化數(shù)據(jù) 1. 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']});
3.與共享線程通信
共享線程的通信也是跟專用線程一樣,是通過使用隱式的 MessagePort 對象實(shí)例來完成的。當(dāng)使用 SharedWorker() 構(gòu)造函數(shù)的時(shí)候,這個(gè)對象將通過一種引用的方式被返回回來。我們可以通過這個(gè)引用的 port 端口屬性來與它進(jìn)行通信。發(fā)送消息與接收消息的代碼示例如下:
- 清單 4. 發(fā)送消息與接收消息代碼
上面示例代碼中,第一個(gè)我們使用 onmessage 事件處理器來接收消息,第二個(gè)使用 postMessage 來發(fā)送普通文本數(shù)據(jù),第三個(gè)使用 postMessage 來發(fā)送結(jié)構(gòu)化的數(shù)據(jù),這里我們使用了 JSON 數(shù)據(jù)格式。
工作線程事件處理模型
當(dāng)工作線程被一個(gè)具有 URL 參數(shù)的構(gòu)造函數(shù)創(chuàng)建的時(shí)候,它需要有一系列的處理流程來處理和記錄它本身的數(shù)據(jù)和狀態(tài)。下面我們給出了工作線程的處理模型如下(注:由于 W3C 中工作線程的規(guī)范依然在更新,您讀到這篇文章的時(shí)候可能看到已不是最新的處理模型,建議參考 W3C 中的最新規(guī)范):
1. 創(chuàng)建一個(gè)獨(dú)立的并行處理環(huán)境,并且在這個(gè)環(huán)境里面異步的運(yùn)行下面的步驟。
2. 如果它的全局作用域是 SharedWorkerGlobalScope 對象,那么把最合適的應(yīng)用程序緩存和它聯(lián)系在一起。
3. 嘗試從它提供的 URL 里面使用 synchronous 標(biāo)志和 force same-origin 標(biāo)志獲取腳本資源。
4. 新腳本創(chuàng)建的時(shí)候會(huì)按照下面的步驟:
- 創(chuàng)建這個(gè)腳本的執(zhí)行環(huán)境。
- 使用腳本的執(zhí)行環(huán)境解析腳本資源。
- 設(shè)置腳本的全局變量為工作線程全局變量。
- 設(shè)置腳本編碼為 UTF-8 編碼。
5. 啟動(dòng)線程監(jiān)視器,關(guān)閉孤兒線程。
6. 對于掛起線程,啟動(dòng)線程監(jiān)視器監(jiān)視掛起線程的狀態(tài),即時(shí)在并行環(huán)境中更改它們的狀態(tài)。
7. 跳入腳本初始點(diǎn),并且啟動(dòng)運(yùn)行。
8. 如果其全局變量為 DedicatedWorkerGlobalScope 對象,然后在線程的隱式端口中啟用端口消息隊(duì)列。
9. 對于事件循環(huán),等待一直到事件循環(huán)列表中出現(xiàn)新的任務(wù)。
10. 首先運(yùn)行事件循環(huán)列表中的最先進(jìn)入的任務(wù),但是用戶代理可以選擇運(yùn)行任何一個(gè)任務(wù)。
11. 如果事件循環(huán)列表擁有存儲(chǔ) mutex 互斥信號量,那么釋放它。
12. 當(dāng)運(yùn)行完一個(gè)任務(wù)后,從事件循環(huán)列表中刪除它。
13. 如果事件循環(huán)列表中還有任務(wù),那么繼續(xù)前面的步驟執(zhí)行這些任務(wù)。
14. 如果活動(dòng)超時(shí)后,清空工作線程的全局作用域列表。
15. 釋放工作線程的端口列表中的所有端口。
#p#
工作線程應(yīng)用范圍和作用域
工作線程的全局作用域僅僅限于工作線程本身,即在線程的生命周期內(nèi)有效。規(guī)范中 WorkerGlobalScope 接口代表了它的全局作用域,下面我們來看下這個(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 屬性來或者這個(gè)對象本身的引用。location 屬性返回當(dāng)線程被創(chuàng)建出來的時(shí)候與之關(guān)聯(lián)的 WorkerLocation 對象,它表示用于初始化這個(gè)工作線程的腳步資源的絕對 URL,即使頁面被多次重定向后,這個(gè) URL 資源位置也不會(huì)改變。
當(dāng)腳本調(diào)用 WorkerGlobalScope 上的 close()方法后,會(huì)自動(dòng)的執(zhí)行下面的兩個(gè)步驟:
1. 刪除這個(gè)工作線程事件隊(duì)列中的所有任務(wù)。
2. 設(shè)置 WorkerGlobalScope 對象的 closing 狀態(tài)為 true (這將阻止以后任何新的任務(wù)繼續(xù)添加到事件隊(duì)列中來)。
工作線程生命周期
工作線程之間的通信必須依賴于瀏覽器的上下文環(huán)境,并且通過它們的 MessagePort 對象實(shí)例傳遞消息。每個(gè)工作線程的全局作用域都擁有這些線程的端口列表,這些列表包括了所有線程使用到的 MessagePort 對象。在專用線程的情況下,這個(gè)列表還會(huì)包含隱式的 MessagePort 對象。
每個(gè)工作線程的全局作用域?qū)ο?WorkerGlobalScope 還會(huì)有一個(gè)工作線程的線程列表,在初始化時(shí)這個(gè)列表為空。當(dāng)工作線程被創(chuàng)建的時(shí)候或者擁有父工作線程的時(shí)候,它們就會(huì)被填充進(jìn)來。
最后,每個(gè)工作線程的全局作用域?qū)ο?WorkerGlobalScope 還擁有這個(gè)線程的文檔模型,在初始化時(shí)這個(gè)列表為空。當(dāng)工作線程被創(chuàng)建的時(shí)候,文檔對象就會(huì)被填充進(jìn)來。無論何時(shí)當(dāng)一個(gè)文檔對象被丟棄的時(shí)候,它就要從這個(gè)文檔對象列舉里面刪除出來。
在工作線程的生命周期中,定義了下面四種不同類型的線程名稱,用以標(biāo)識它們在線程的整個(gè)生命周期中的不同狀態(tài):
- 當(dāng)一個(gè)工作線程的文檔對象列舉不為空的時(shí)候,這個(gè)工作線程會(huì)被稱之為許可線程。(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ù)庫事務(wù)或者擁有網(wǎng)絡(luò)連接或者它的工作線程列表不為空的時(shí)候,這個(gè)工作線程會(huì)被稱之為受保護(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è)工作線程的文檔對象列表中的任何一個(gè)對象都是處于完全活動(dòng)狀態(tài)的時(shí)候,這個(gè)工作線程會(huì)被稱之為需要激活線程。(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ì)被稱之為掛起線程。(A worker is said to be a suspendable worker if it is not an active needed worker but it is a permissible worker.)
由于 W3C 的 Web Worker 規(guī)范目前還是處于完善階段,沒有形成最終的規(guī)范,本文也將上面線程的四種不同狀態(tài)的原文定義附在了后面。
工作線程(Web Worker)API 接口
類庫和腳本的訪問和引入
對于類庫和腳本的訪問和引入,規(guī)范中規(guī)定可以使用 WorkerGlobalScope 對象的 importScripts(urls) 方法來引入網(wǎng)絡(luò)中的腳本資源。當(dāng)用戶調(diào)用這個(gè)方法引入資源的時(shí)候會(huì)執(zhí)行下面的步驟來完成這個(gè)操作:
- 如果沒有給 importScripts 方法任何參數(shù),那么立即返回,終止下面的步驟。
- 解析 importScripts 方法的每一個(gè)參數(shù)。
- 如果有任何失敗或者錯(cuò)誤,拋出 SYNTAX_ERR 異常。
- 嘗試從用戶提供的 URL 資源位置處獲取腳本資源。
- 對于 importScripts 方法的每一個(gè)參數(shù),按照用戶的提供順序,獲取腳本資源后繼續(xù)進(jìn)行其它操作。
- 清單 6. 外部資源腳本引入和訪問示例代碼
- /**
- * 使用 importScripts 方法引入外部資源腳本,在這里我們使用了數(shù)學(xué)公式計(jì)算工具庫 math_utilities.js
- * 當(dāng) JavaScript 引擎對這個(gè)資源文件加載完畢后,繼續(xù)執(zhí)行下面的代碼。同時(shí),下面的的代碼可以訪問和調(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)航器對象(WorkerNavigator)
在 HTML5 中, WorkerUtils 接口的 navigator 屬性會(huì)返回一個(gè)工作導(dǎo)航器對象(WorkerNavigator),這個(gè)對象定義并且代表了用戶代理(即 Web 客戶端)的標(biāo)識和狀態(tài)。因此,用戶和 Web 腳本開發(fā)人員可以在多線程開發(fā)過程中通過這個(gè)對象來取得或者確定用戶的狀態(tài)。
- 工作導(dǎo)航器對象(WorkerNavigator)
WorkerUtils 抽象接口的 navigator 屬性會(huì)返回一個(gè) WorkerNavigator 用戶接口,用于用戶代理的識別的狀態(tài)標(biāo)識。我們來看下 WorkerNavigator 接口的定義。
- WorkerNavigator 接口定義
清單 7. WorkerNavigator 接口定義代碼 - interface WorkerNavigator {};
- WorkerNavigator implements NavigatorID;
- WorkerNavigator implements NavigatorOnLine;
其中,有一點(diǎn)需要注意:如果接口的相對命名空間對象為 Window 對象的時(shí)候,WorkerNavigator 對象一定不可以存在,即無法再使用這個(gè)對象。
創(chuàng)建與終止線程
在講解創(chuàng)建新的工作線程之前,我們先看下 W3C 規(guī)范對工作線程的定義。工作線程規(guī)范中定義了線程的抽象接口類 AbstractWorker ,專用線程以及共享線程都繼承自該抽象接口。專用線程以及共享線程的創(chuàng)建方法讀者可以參考第一小節(jié)中的示例代碼。下面是此抽象接口的定義。
1.AbstractWorker 抽象接口
- 清單 8. AbstractWorker 抽象接口代碼
此外,該接口還定義了錯(cuò)誤處理的事件處理器 onerror,當(dāng)工作線程在通信過程中遇到錯(cuò)誤時(shí)便會(huì)觸發(fā)這個(gè)事件處理器。
2.專用線程及其定義
- 清單 9. 專用線程定義代碼
- [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è)專用線程都擁有一個(gè)隱式的 MessagePort 對象與之相關(guān)聯(lián)。這個(gè)端口隨著線程的創(chuàng)建而被創(chuàng)建出來,但并沒有暴露給用戶。所有的基于這個(gè)端口的消息接收都以線程本身為目標(biāo)。
3.共享線程及其定義
- 清單 10. 共享線程定義代碼
- [Constructor(DOMString scriptURL, optional DOMString name)]
- interface SharedWorker : AbstractWorker {
- readonly attribute MessagePort port;
- };
共享線程同專用線程一樣,當(dāng)創(chuàng)建完線程以后,我們可以調(diào)用 terminate() 方法去終止一個(gè)共享線程。
#p#
工作線程位置屬性
工作線程被創(chuàng)建出來以后,需要記錄它的狀態(tài)以及位置信息,在工作線程規(guī)范中定義了 WorkerLocation 來表示它們的位置。接口定義如下:
- 清單 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 對象表示了工作線程腳本資源的絕對 URL 信息。我們可以使用它的 href 屬性取得這個(gè)對象的絕對 URL。WorkerLocation 接口還定義了與位置信息有關(guān)的其它屬性,例如:用于信息傳輸?shù)膮f(xié)議(protocol),主機(jī)名稱(hostname),端口(port),路徑名稱(pathname)等。
工作線程(Web Worker)應(yīng)用與實(shí)踐
我們可以寫出很多的例子來說明后臺工作線程的合適的用法,下面我們以幾種典型的應(yīng)用場景為例,用代碼實(shí)例的形式講解在各種需求背景下正確的使用它們。
應(yīng)用場景一:使用工作線程做后臺數(shù)值(算法)計(jì)算
工作線程最簡單的應(yīng)用就是用來做后臺計(jì)算,而這種計(jì)算并不會(huì)中斷前臺用戶的操作。下面我們提供了一個(gè)工作線程的代碼片段,用來執(zhí)行一個(gè)相對來說比較復(fù)雜的任務(wù):計(jì)算兩個(gè)非常大的數(shù)字的最小公倍數(shù)和最大公約數(shù)。
在這個(gè)例子中,我們在主頁面中創(chuàng)建一個(gè)后臺工作線程,并且向這個(gè)工作線程分配任務(wù)(即傳遞兩個(gè)特別大的數(shù)字),當(dāng)工作線程執(zhí)行完這個(gè)任務(wù)時(shí),便向主頁面程序返回計(jì)算結(jié)果,而在這個(gè)過程中,主頁面不需要等待這個(gè)耗時(shí)的操作,可以繼續(xù)進(jìn)行其它的行為或任務(wù)。
我們把這個(gè)應(yīng)用場景分為兩個(gè)主要部分,一個(gè)是主頁面,可以包含主 JavaScript 應(yīng)用入口,用戶其它操作 UI 等。另外一個(gè)是后臺工作線程腳本,即用來執(zhí)行計(jì)算任務(wù)。代碼片段如下:
- 清單 12. 主程序頁面代碼
- <!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. 后臺工作線程代碼
- /**
- * 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; }
在主程序頁面中,我們使用 Worker()構(gòu)造函數(shù)創(chuàng)建一個(gè)新的工作線程,它會(huì)返回一個(gè)代表此線程本身的線程對象。接下來我們使用這個(gè)線程對象與后臺腳本進(jìn)行通信。線程對象有兩個(gè)主要事件處理器:postMessage 和 onmessage 。postMessage 用來向后臺腳本發(fā)送消息,onmessage 用以接收從后臺腳本中傳遞過來的消息。
在后臺工作線程代碼片段中,我們定一個(gè)兩個(gè) JavaScript 函數(shù),一個(gè)是 function divisor:用以計(jì)算最大公約數(shù),一個(gè)是 function multiple:用以計(jì)算最小公倍數(shù)。同時(shí)工作線程的 onmessage 事件處理器用以接收從主頁面中傳遞過來的數(shù)值,然后把這兩個(gè)數(shù)值傳遞到 function calculate 用以計(jì)算。當(dāng)計(jì)算完成后,調(diào)用事件處理器 postMessage,把計(jì)算結(jié)果發(fā)送到主頁面。
應(yīng)用場景二:使用共享線程處理多用戶并發(fā)連接
由于線程的構(gòu)建以及銷毀都要消耗很多的系統(tǒng)性能,例如 CPU 的處理器調(diào)度,內(nèi)存的占用回收等,在一般的編程語言中都會(huì)有線程池的概念,線程池是一種對多線程并發(fā)處理的形式,在處理過程中系統(tǒng)將所有的任務(wù)添加到一個(gè)任務(wù)隊(duì)列,然后在構(gòu)建好線程池以后自動(dòng)啟動(dòng)這些任務(wù)。處理完任務(wù)后再把線程收回到線程池中,用于下一次任務(wù)調(diào)用。線程池也是共享線程的一種應(yīng)用。
在 HTML5 中也引入了共享線程技術(shù),但是由于每個(gè)共享線程可以有多個(gè)連接,HTML5 對共享線程提供了和普通工作線程稍微有些區(qū)別的 API 接口。下面我們提供幾個(gè)例子來講述對共享線程的用法。
下面我們給出一個(gè)例子:創(chuàng)建一個(gè)共享線程用于接收從不同連接發(fā)送過來的指令,然后實(shí)現(xiàn)自己的指令處理邏輯,指令處理完成后將結(jié)果返回到各個(gè)不同的連接用戶。
- 清單 14. 共享線程用戶連接頁面代碼
- <!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>
- // 創(chuàng)建一個(gè)共享線程用于接收從不同連接發(fā)送過來的指令,指令處理完成后將結(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_numberconnect_numberconnect_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 }
#p#
- 清單 16. 主頁面(僅僅是用來顯示計(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); } }
- //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)開發(fā)的程序人員提供了強(qiáng)大的并發(fā)程序設(shè)計(jì)功能,它允許開發(fā)人員設(shè)計(jì)開發(fā)出性能和交互更好的富客戶端應(yīng)用程序。本文不僅僅詳細(xì)講述 HTML5 中的多線程規(guī)范。同時(shí),也以幾種典型的應(yīng)用場景為例,以實(shí)例的形式講解 HTML5 中多線程編程以及應(yīng)用,為用戶提供了詳細(xì)而全面的參考價(jià)值,并且指導(dǎo)開發(fā)人員設(shè)計(jì)和構(gòu)建更為高效和穩(wěn)定的 Web 多線程應(yīng)用。
原文鏈接:http://www.ibm.com/developerworks/cn/web/1112_sunch_webworker/index.html