深刻 HTML5 Web Worker 應用實踐:多線程編程

深刻 HTML5 Web Worker 應用實踐:多線程編程

HTML5 中工做線程(Web Worker)簡介

至 2008 年 W3C 制定出第一個 HTML5 草案開始,HTML5 承載了愈來愈多嶄新的特性和功能。它不但強化了 Web 系統或網頁的表現性能,並且還增長了對本地數據庫等 Web 應用功能的支持。其中,最重要的一個即是對多線程的支持。在 HTML5 中提出了工做線程(Web Worker)的概念,而且規範出 Web Worker 的三大主要特徵:可以長時間運行(響應),理想的啓動性能以及理想的內存消耗。Web Worker 容許開發人員編寫可以長時間運行而不被用戶所中斷的後臺程序,去執行事務或者邏輯,並同時保證頁面對用戶的及時響應。本文深刻 HTML5 多線程規範,講述多線程實現原理、方法,同時以實例的形式講解 HTML5 中多線程編程以及應用。html

W3C 中的工做線程規範到目前爲止已經定義了出了一系列公共接口,它容許 Web 程序開發人員去建立後臺線程在他們的主頁面中併發的運行腳本。這將使得線程級別的消息通訊成爲現實。html5

詳解 HTML5 工做線程原理

傳統上的線程能夠解釋爲輕量級進程,它和進程同樣擁有獨立的執行控制,通常狀況下由操做系統負責調度。而在 HTML5 中的多線程是這樣一種機制,它容許在 Web 程序中併發執行多個 JavaScript 腳本,每一個腳本執行流都稱爲一個線程,彼此間互相獨立,而且有瀏覽器中的 JavaScript 引擎負責管理。下面咱們將詳細講解 HTML5 的工做線程原理。web

工做線程與多線程編程

在 HTML5 中,工做線程的出現使得在 Web 頁面中進行多線程編程成爲可能。衆所周知,傳統頁面中(HTML5 以前)的 JavaScript 的運行都是以單線程的方式工做的,雖然有多種方式實現了對多線程的模擬(例如:JavaScript 中的 setinterval 方法,setTimeout 方法等),可是在本質上程序的運行仍然是由 JavaScript 引擎以單線程調度的方式進行的。在 HTML5 中引入的工做線程使得瀏覽器端的 JavaScript 引擎能夠併發地執行 JavaScript 代碼,從而實現了對瀏覽器端多線程編程的良好支持。ajax

HTML5 中的 Web Worker 能夠分爲兩種不一樣線程類型,一個是專用線程 Dedicated Worker,一個是共享線程 Shared Worker。兩種類型的線程各有不一樣的用途。下面對這兩種工做線程做了詳細的說明和描述。算法

專用線程:Dedicated Worker

  1. 專用線程(dedicated worker)的建立方式:

    在建立專用線程的時候,須要給 Worker 的構造函數提供一個指向 JavaScript 文件資源的 URL,這也是建立專用線程時 Worker 構造函數所須要的惟一參數。當這個構造函數被調用以後,一個工做線程的實例便會被建立出來。下面是建立專用線程代碼示例:數據庫

    清單 1. 建立專用線程示例代碼
    1
    var worker = new Worker('dedicated.js');
  2. 與一個專用線程通訊:

    專用線程在運行的過程當中會在後臺使用 MessagePort 對象,而 MessagePort 對象支持 HTML5 中多線程提供的全部功能,例如:能夠發送和接受結構化數據(JSON 等),傳輸二進制數據,而且支持在不一樣端口中傳輸數據等。編程

    爲了在頁面主程序接收從專用線程傳遞過來的消息,咱們須要使用工做線程的 onmessage 事件處理器,定義 onmessage 的實例代碼以下:json

    清單 2. 接收來至工做線程示例代碼
    1
    worker.onmessage = function (event) { ... };

    另外,開發人員也能夠選擇使用 addEventListener 方法,它最終的實現方式和做用和 onmessage 相同。瀏覽器

    就像前面講述的,專用線程會使用隱式的 MessagePort 實例,當專用線程被建立的時候,MessagePort 的端口消息隊列便被主動啓用。所以,這也和工做線程接口中定義的 start 方法做用一致。緩存

    若是要想一個專用線程發送數據,那麼咱們須要使用線程中的 postMessage 方法。專用線程不只僅支持傳輸二進制數據,也支持結構化的 JavaScript 數據格式。在這裏有一點須要注意,爲了高效地傳輸 ArrayBuffer 對象數據,須要在 postMessage 方法中的第二個參數中指定它。實例代碼以下:

    清單 3. 高效的發送 ArrayBuffer 數據代碼
    1
    2
    3
    4
    5
    6
    worker.postMessage({
      operation: 'list_all_users',
      //ArrayBuffer object
      input: buffer,
      threshold: 0.8,
    }, [buffer]);

共享線程 Shared Worker

  1. 共享線程

    共享線程能夠由兩種方式來定義:一是經過指向 JavaScript 腳本資源的 URL 來建立,而是經過顯式的名稱。當由顯式的名稱來定義的時候,由建立這個共享線程的第一個頁面中使用 URL 會被用來做爲這個共享線程的 JavaScript 腳本資源 URL。經過這樣一種方式,它容許同域中的多個應用程序使用同一個提供公共服務的共享線程,從而不須要全部的應用程序都去與這個提供公共服務的 URL 保持聯繫。

    不管在什麼狀況下,共享線程的做用域或者是生效範圍都是由建立它的域來定義的。所以,兩個不一樣的站點(即域)使用相同的共享線程名稱也不會衝突。

  2. 共享線程的建立

    建立共享線程能夠經過使用 SharedWorker() 構造函數來實現,這個構造函數使用 URL 做爲第一個參數,便是指向 JavaScript 資源文件的 URL,同時,若是開發人員提供了第二個構造參數,那麼這個參數將被用於做爲這個共享線程的名稱。建立共享線程的代碼示例以下:

    var worker = new SharedWorker('sharedworker.js', ’ mysharedworker ’ );

  3. 與共享線程通訊

    共享線程的通訊也是跟專用線程同樣,是經過使用隱式的 MessagePort 對象實例來完成的。當使用 SharedWorker() 構造函數的時候,這個對象將經過一種引用的方式被返回回來。咱們能夠經過這個引用的 port 端口屬性來與它進行通訊。發送消息與接收消息的代碼示例以下:

    清單 4. 發送消息與接收消息代碼
    1
    2
    3
    4
    5
    6
    7
    // 從端口接收數據 , 包括文本數據以及結構化數據
    1. worker.port.onmessage = function (event) { define your logic here... };
    // 向端口發送普通文本數據
    2. worker.port.postMessage('put your message here … ');
    // 向端口發送結構化數據
    3. worker.port.postMessage({ username: 'usertext'; live_city:
    ['data-one', 'data-two', 'data-three','data-four']});

    上面示例代碼中,第一個咱們使用 onmessage 事件處理器來接收消息,第二個使用 postMessage 來發送普通文本數據,第三個使用 postMessage 來發送結構化的數據,這裏咱們使用了 JSON 數據格式。

工做線程事件處理模型

當工做線程被一個具備 URL 參數的構造函數建立的時候,它須要有一系列的處理流程來處理和記錄它自己的數據和狀態。下面咱們給出了工做線程的處理模型以下(注:因爲 W3C 中工做線程的規範依然在更新,您讀到這篇文章的時候可能看到已不是最新的處理模型,建議參考 W3C 中的最新規範):

1. 建立一個獨立的並行處理環境,而且在這個環境裏面異步的運行下面的步驟。

2. 若是它的全局做用域是 SharedWorkerGlobalScope 對象,那麼把最合適的應用程序緩存和它聯繫在一塊兒。

3. 嘗試從它提供的 URL 裏面使用 synchronous 標誌和 force same-origin 標誌獲取腳本資源。

4. 新腳本建立的時候會按照下面的步驟:

  1. 建立這個腳本的執行環境。
  2. 使用腳本的執行環境解析腳本資源。
  3. 設置腳本的全局變量爲工做線程全局變量。
  4. 設置腳本編碼爲 UTF-8 編碼。

5. 啓動線程監視器,關閉孤兒線程。

6. 對於掛起線程,啓動線程監視器監視掛起線程的狀態,即時在並行環境中更改它們的狀態。

7. 跳入腳本初始點,而且啓動運行。

8. 若是其全局變量爲 DedicatedWorkerGlobalScope 對象,而後在線程的隱式端口中啓用端口消息隊列。

9. 對於事件循環,等待一直到事件循環列表中出現新的任務。

10. 首先運行事件循環列表中的最早進入的任務,可是用戶代理能夠選擇運行任何一個任務。

11. 若是事件循環列表擁有存儲 mutex 互斥信號量,那麼釋放它。

12. 當運行完一個任務後,從事件循環列表中刪除它。

13. 若是事件循環列表中還有任務,那麼繼續前面的步驟執行這些任務。

14. 若是活動超時後,清空工做線程的全局做用域列表。

15. 釋放工做線程的端口列表中的全部端口。

工做線程應用範圍和做用域

工做線程的全局做用域僅僅限於工做線程自己,即在線程的生命週期內有效。規範中 WorkerGlobalScope 接口表明了它的全局做用域,下面咱們來看下這個接口的具體實施細節(WorkerGlobalScope 抽象接口)。

清單 5. WorkerGlobalScope 抽象接口代碼
1
2
3
4
5
6
7
8
9
interface WorkerGlobalScope {
  readonly attribute WorkerGlobalScope self;
  readonly attribute WorkerLocation location;
 
  void close();
           attribute Function onerror;
};
WorkerGlobalScope implements WorkerUtils;
WorkerGlobalScope implements EventTarget;

咱們可使用 WorkerGlobalScope 的 self 屬性來或者這個對象自己的引用。location 屬性返回當線程被建立出來的時候與之關聯的 WorkerLocation 對象,它表示用於初始化這個工做線程的腳步資源的絕對 URL,即便頁面被屢次重定向後,這個 URL 資源位置也不會改變。

當腳本調用 WorkerGlobalScope 上的 close()方法後,會自動的執行下面的兩個步驟:

1. 刪除這個工做線程事件隊列中的全部任務。

2. 設置 WorkerGlobalScope 對象的 closing 狀態爲 true (這將阻止之後任何新的任務繼續添加到事件隊列中來)。

工做線程生命週期

工做線程之間的通訊必須依賴於瀏覽器的上下文環境,而且經過它們的 MessagePort 對象實例傳遞消息。每一個工做線程的全局做用域都擁有這些線程的端口列表,這些列表包括了全部線程使用到的 MessagePort 對象。在專用線程的狀況下,這個列表還會包含隱式的 MessagePort 對象。

每一個工做線程的全局做用域對象 WorkerGlobalScope 還會有一個工做線程的線程列表,在初始化時這個列表爲空。當工做線程被建立的時候或者擁有父工做線程的時候,它們就會被填充進來。

最後,每一個工做線程的全局做用域對象 WorkerGlobalScope 還擁有這個線程的文檔模型,在初始化時這個列表爲空。當工做線程被建立的時候,文檔對象就會被填充進來。不管什麼時候當一個文檔對象被丟棄的時候,它就要從這個文檔對象列舉裏面刪除出來。

在工做線程的生命週期中,定義了下面四種不一樣類型的線程名稱,用以標識它們在線程的整個生命週期中的不一樣狀態:

  • 當一個工做線程的文檔對象列舉不爲空的時候,這個工做線程會被稱之爲許可線程。(A worker is said to be a permissible worker if its list of the worker's Documents is not empty.)
  • 當一個工做線程是許可線程而且或者擁有數據庫事務或者擁有網絡鏈接或者它的工做線程列表不爲空的時候,這個工做線程會被稱之爲受保護的線程。(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)
  • 當一個工做線程的文檔對象列表中的任何一個對象都是處於徹底活動狀態的時候,這個工做線程會被稱之爲須要激活線程。(A worker is said to be an active needed worker if any of the Document objects in the worker's Documents are fully active.)
  • 當一個工做線程是一個非須要激活線程同時又是一個許可線程的時候,這個工做線程會被稱之爲掛起線程。(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 規範目前仍是處於完善階段,沒有造成最終的規範,本文也將上面線程的四種不一樣狀態的原文定義附在了後面。

工做線程(Web Worker)API 接口

類庫和腳本的訪問和引入

對於類庫和腳本的訪問和引入,規範中規定可使用 WorkerGlobalScope 對象的 importScripts(urls) 方法來引入網絡中的腳本資源。當用戶調用這個方法引入資源的時候會執行下面的步驟來完成這個操做:

  1. 若是沒有給 importScripts 方法任何參數,那麼當即返回,終止下面的步驟。
  2. 解析 importScripts 方法的每個參數。
  3. 若是有任何失敗或者錯誤,拋出 SYNTAX_ERR 異常。
  4. 嘗試從用戶提供的 URL 資源位置處獲取腳本資源。
  5. 對於 importScripts 方法的每個參數,按照用戶的提供順序,獲取腳本資源後繼續進行其它操做。
清單 6. 外部資源腳本引入和訪問示例代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
  * 使用 importScripts 方法引入外部資源腳本,在這裏咱們使用了數學公式計算工具庫 math_utilities.js
  * 當 JavaScript 引擎對這個資源文件加載完畢後,繼續執行下面的代碼。同時,下面的的代碼能夠訪問和調用
  * 在資源文件中定義的變量和方法。
  **/
  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);
  }

工做導航器對象(WorkerNavigator)

在 HTML5 中, WorkerUtils 接口的 navigator 屬性會返回一個工做導航器對象(WorkerNavigator),這個對象定義而且表明了用戶代理(即 Web 客戶端)的標識和狀態。所以,用戶和 Web 腳本開發人員能夠在多線程開發過程當中經過這個對象來取得或者肯定用戶的狀態。

  1. 工做導航器對象(WorkerNavigator)

    WorkerUtils 抽象接口的 navigator 屬性會返回一個 WorkerNavigator 用戶接口,用於用戶代理的識別的狀態標識。咱們來看下 WorkerNavigator 接口的定義。

  2. WorkerNavigator 接口定義
    清單 7. WorkerNavigator 接口定義代碼
    1
    2
    3
    interface WorkerNavigator {};
    WorkerNavigator implements NavigatorID;
    WorkerNavigator implements NavigatorOnLine;

    其中,有一點須要注意:若是接口的相對命名空間對象爲 Window 對象的時候,WorkerNavigator 對象必定不能夠存在,即沒法再使用這個對象。

建立與終止線程

在講解建立新的工做線程以前,咱們先看下 W3C 規範對工做線程的定義。工做線程規範中定義了線程的抽象接口類 AbstractWorker ,專用線程以及共享線程都繼承自該抽象接口。專用線程以及共享線程的建立方法讀者能夠參考第一小節中的示例代碼。下面是此抽象接口的定義。

  1. AbstractWorker 抽象接口
    清單 8. AbstractWorker 抽象接口代碼
    1
    2
    3
    4
    5
    6
    [Supplemental, NoInterfaceObject]
    interface AbstractWorker {
               attribute Function onerror;
     
    };
    AbstractWorker implements EventTarget;

    此外,該接口還定義了錯誤處理的事件處理器 onerror,當工做線程在通訊過程當中遇到錯誤時便會觸發這個事件處理器。

  2. 專用線程及其定義
    清單 9. 專用線程定義代碼
    1
    2
    3
    4
    5
    6
    7
    [Constructor(in DOMString scriptURL)]
    interface Worker : AbstractWorker {
      void terminate();
     
      void postMessage(in any message, in optional MessagePortArray ports);
               attribute Function onmessage;
    };

    當建立完線程之後,咱們能夠調用 terminate() 方法去終止一個線程。每一個專用線程都擁有一個隱式的 MessagePort 對象與之相關聯。這個端口隨着線程的建立而被建立出來,但並無暴露給用戶。全部的基於這個端口的消息接收都以線程自己爲目標。

  3. 共享線程及其定義
    清單 10. 共享線程定義代碼
    1
    2
    3
    4
    [Constructor(DOMString scriptURL, optional DOMString name)]
    interface SharedWorker : AbstractWorker {
    readonly attribute MessagePort port;
    };

    共享線程同專用線程同樣,當建立完線程之後,咱們能夠調用 terminate() 方法去終止一個共享線程。

工做線程位置屬性

工做線程被建立出來之後,須要記錄它的狀態以及位置信息,在工做線程規範中定義了 WorkerLocation 來表示它們的位置。接口定義以下:

清單 11. 共享線程定義代碼
1
2
3
4
5
6
7
8
9
10
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 屬性取得這個對象的絕對 URL。WorkerLocation 接口還定義了與位置信息有關的其它屬性,例如:用於信息傳輸的協議(protocol),主機名稱(hostname),端口(port),路徑名稱(pathname)等。

工做線程(Web Worker)應用與實踐

咱們能夠寫出不少的例子來講明後臺工做線程的合適的用法,下面咱們以幾種典型的應用場景爲例,用代碼實例的形式講解在各類需求背景下正確的使用它們。

應用場景一:使用工做線程作後臺數值(算法)計算

工做線程最簡單的應用就是用來作後臺計算,而這種計算並不會中斷前臺用戶的操做。下面咱們提供了一個工做線程的代碼片斷,用來執行一個相對來講比較複雜的任務:計算兩個很是大的數字的最小公倍數和最大公約數。

在這個例子中,咱們在主頁面中建立一個後臺工做線程,而且向這個工做線程分配任務(即傳遞兩個特別大的數字),當工做線程執行完這個任務時,便向主頁面程序返回計算結果,而在這個過程當中,主頁面不須要等待這個耗時的操做,能夠繼續進行其它的行爲或任務。

咱們把這個應用場景分爲兩個主要部分,一個是主頁面,能夠包含主 JavaScript 應用入口,用戶其它操做 UI 等。另一個是後臺工做線程腳本,即用來執行計算任務。代碼片斷以下:

清單 12. 主程序頁面代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!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. 後臺工做線程代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
  * 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()構造函數建立一個新的工做線程,它會返回一個表明此線程自己的線程對象。接下來咱們使用這個線程對象與後臺腳本進行通訊。線程對象有兩個主要事件處理器:postMessage 和 onmessage 。postMessage 用來向後臺腳本發送消息,onmessage 用以接收從後臺腳本中傳遞過來的消息。

在後臺工做線程代碼片斷中,咱們定一個兩個 JavaScript 函數,一個是 function divisor:用以計算最大公約數,一個是 function multiple:用以計算最小公倍數。同時工做線程的 onmessage 事件處理器用以接收從主頁面中傳遞過來的數值,而後把這兩個數值傳遞到 function calculate 用以計算。當計算完成後,調用事件處理器 postMessage,把計算結果發送到主頁面。

應用場景二:使用共享線程處理多用戶併發鏈接

因爲線程的構建以及銷燬都要消耗不少的系統性能,例如 CPU 的處理器調度,內存的佔用回收等,在通常的編程語言中都會有線程池的概念,線程池是一種對多線程併發處理的形式,在處理過程當中系統將全部的任務添加到一個任務隊列,而後在構建好線程池之後自動啓動這些任務。處理完任務後再把線程收回到線程池中,用於下一次任務調用。線程池也是共享線程的一種應用。

在 HTML5 中也引入了共享線程技術,可是因爲每一個共享線程能夠有多個鏈接,HTML5 對共享線程提供了和普通工做線程稍微有些區別的 API 接口。下面咱們提供幾個例子來說述對共享線程的用法。

下面咱們給出一個例子:建立一個共享線程用於接收從不一樣鏈接發送過來的指令,而後實現本身的指令處理邏輯,指令處理完成後將結果返回到各個不一樣的鏈接用戶。

清單 14. 共享線程用戶鏈接頁面代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!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. 用於處理用戶指令的共享線程代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 建立一個共享線程用於接收從不一樣鏈接發送過來的指令,指令處理完成後將結果返回到各個不一樣的鏈接用戶。
 
/*
* 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
}

在上面的共享線程例子中,在主頁面即各個用戶鏈接頁面構造出一個共享線程對象,而後定義了一個方法 postMessageToSharedWorker 向共享線程發送來之用戶的指令。同時,在共享線程的實現代碼片斷中定義 connect_number 用來記錄鏈接到這個共享線程的總數。以後,用 onconnect 事件處理器接受來自不一樣用戶的鏈接,解析它們傳遞過來的指令。最後,定義一個了方法 execute_instruction 用於執行用戶的指令,指令執行完成後將結果返回給各個用戶。

這裏咱們並無跟前面的例子同樣使用到了工做線程的 onmessage 事件處理器,而是使用了另一種方式 addEventListener。實際上,這兩種的實現原理基本一致,只有有些稍微的差異,若是使用到了 addEventListener 來接受來自共享線程的消息,那麼就要使用 worker.port.start() 方法來啓動這個端口。以後就能夠像工做線程的使用方式同樣正常的接收和發送消息。

應用場景三:HTML5 線程代理

多線程代理技術

隨着多核處理器的流行,現代的計算機通常都擁有多核的 CPU,這也使得任務可以在處理器級別上併發執行。若是咱們要在一個具備多核 CPU 的客戶端上用單線程去執行程序即處理業務邏輯,每每不能最大化的利用系統資源。所以,在這種狀況下咱們能夠將一個耗時或者複雜的任務拆分紅多個子任務,把每個子任務分擔給一個工做線程,這樣多個工做現場就共同承擔了單個線程的工做負載,同時又可以併發的去執行,最大化的利用了系統資源(CPU、內存、I/O 等)。

下面咱們向讀者提供一個線程代理應用的例子:計算全球人口的數量。

清單 16. 主頁面(僅僅是用來顯示計算結果)代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!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. 主工做線程代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* 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. 代理線程代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//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();
}

總結

HTML5 Web Worker 的多線程特性爲基於 Web 系統開發的程序人員提供了強大的併發程序設計功能,它容許開發人員設計開發出性能和交互更好的富客戶端應用程序。本文不只僅詳細講述 HTML5 中的多線程規範。同時,也以幾種典型的應用場景爲例,以實例的形式講解 HTML5 中多線程編程以及應用,爲用戶提供了詳細而全面的參考價值,而且指導開發人員設計和構建更爲高效和穩定的 Web 多線程應用。

相關主題

相關文章
相關標籤/搜索