溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

HTML5中如何進(jìn)行多線程編程應(yīng)用

發(fā)布時(shí)間:2021-10-12 16:41:25 來(lái)源:億速云 閱讀:113 作者:柒染 欄目:編程語(yǔ)言

HTML5中如何進(jìn)行多線程編程應(yīng)用,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

HTML5 中工作線程(Web  Worker)簡(jiǎn)介

至 2008 年 W3C 制定出第一個(gè) HTML5 草案開(kāi)始,HTML5  承載了越來(lái)越多嶄新的特性和功能。它不但強(qiáng)化了 Web 系統(tǒng)或網(wǎng)頁(yè)的表現(xiàn)性能,而且還增加了對(duì)本地?cái)?shù)據(jù)庫(kù)等 Web  應(yīng)用功能的支持。其中,最重要的一個(gè)便是對(duì)多線程的支持。在 HTML5 中提出了工作線程(Web Worker)的概念,并且規(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í)行流都稱為一個(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 可以分為兩種不同線程類型,一個(gè)是專用線程 Dedicated  Worker,一個(gè)是共享線程 Shared Worker。兩種類型的線程各有不同的用途。下面對(duì)這兩種工作線程作了詳細(xì)的說(shuō)明和描述。

專用線程: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)建出來(lái)。下面是創(chuàng)建專用線程代碼示例:

  • 清單 1. 創(chuàng)建專用線程示例代碼

  1. var worker = new Worker('dedicated.js');  

2. 與一個(gè)專用線程通信: 

專用線程在運(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è)面主程序接收從專用線程傳遞過(guò)來(lái)的消息,我們需要使用工作線程的 onmessage  事件處理器,定義 onmessage 的實(shí)例代碼如下:

  •  清單 2.  接收來(lái)至工作線程示例代碼

worker.onmessage = function (event) { ... };

3.另外,開(kāi)發(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  對(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

1.共享線程    

共享線程可以由兩種方式來(lái)定義:一是通過(guò)指向 JavaScript 腳本資源的 URL  來(lái)創(chuàng)建,而是通過(guò)顯式的名稱。當(dā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)(即域)使用相同的共享線程名稱也不會(huì)沖突。

2.共享線程的創(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è)共享線程的名稱。創(chuàng)建共享線程的代碼示例如下:

  1. / 從端口接收數(shù)據(jù) , 包括文本數(shù)據(jù)以及結(jié)構(gòu)化數(shù)據(jù) 1. worker.port.onmessage = function (event) { define your logic here... };  // 向端口發(fā)送普通文本數(shù)據(jù)  

  2.  

  3. worker.port.postMessage('put your message here … ');  // 向端口發(fā)送結(jié)構(gòu)化數(shù)據(jù)  

  4.  worker.port.postMessage({ username: 'usertext'; live_city:  ['data-one', 'data-two', 'data-three','data-four']});

3.與共享線程通信

共享線程的通信也是跟專用線程一樣,是通過(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ā)送消息與接收消息代碼

上面示例代碼中,第一個(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)。下面我們給出了工作線程的處理模型如下(注:由于 W3C  中工作線程的規(guī)范依然在更新,您讀到這篇文章的時(shí)候可能看到已不是最新的處理模型,建議參考 W3C 中的最新規(guī)范):

1. 創(chuàng)建一個(gè)獨(dú)立的并行處理環(huán)境,并且在這個(gè)環(huán)境里面異步的運(yùn)行下面的步驟。

2. 如果它的全局作用域是 SharedWorkerGlobalScope  對(duì)象,那么把最合適的應(yīng)用程序緩存和它聯(lián)系在一起。

3. 嘗試從它提供的 URL 里面使用 synchronous 標(biāo)志和 force  same-origin 標(biāo)志獲取腳本資源。

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

  1. 創(chuàng)建這個(gè)腳本的執(zhí)行環(huán)境。

  2. 使用腳本的執(zhí)行環(huán)境解析腳本資源。

  3. 設(shè)置腳本的全局變量為工作線程全局變量。

  4. 設(shè)置腳本編碼為 UTF-8 編碼。

5. 啟動(dòng)線程監(jiān)視器,關(guān)閉孤兒線程。

6. 對(duì)于掛起線程,啟動(dòng)線程監(jiān)視器監(jiān)視掛起線程的狀態(tài),即時(shí)在并行環(huán)境中更改它們的狀態(tài)。

7. 跳入腳本初始點(diǎn),并且啟動(dòng)運(yùn)行。

8. 如果其全局變量為 DedicatedWorkerGlobalScope  對(duì)象,然后在線程的隱式端口中啟用端口消息隊(duì)列。

9. 對(duì)于事件循環(huán),等待一直到事件循環(huán)列表中出現(xiàn)新的任務(wù)。

10. 首先運(yùn)行事件循環(huán)列表中的最先進(jìn)入的任務(wù),但是用戶代理可以選擇運(yùn)行任何一個(gè)任務(wù)。

11. 如果事件循環(huán)列表?yè)碛写鎯?chǔ) mutex 互斥信號(hào)量,那么釋放它。

12. 當(dāng)運(yùn)行完一個(gè)任務(wù)后,從事件循環(huán)列表中刪除它。

13. 如果事件循環(huán)列表中還有任務(wù),那么繼續(xù)前面的步驟執(zhí)行這些任務(wù)。

14. 如果活動(dòng)超時(shí)后,清空工作線程的全局作用域列表。

15. 釋放工作線程的端口列表中的所有端口。

工作線程應(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è)步驟:

1. 刪除這個(gè)工作線程事件隊(duì)列中的所有任務(wù)。

2. 設(shè)置 WorkerGlobalScope 對(duì)象的 closing 狀態(tài)為 true  (這將阻止以后任何新的任務(wù)繼續(xù)添加到事件隊(duì)列中來(lái))。

工作線程生命周期

工作線程之間的通信必須依賴于瀏覽器的上下文環(huán)境,并且通過(guò)它們的 MessagePort  對(duì)象實(shí)例傳遞消息。每個(gè)工作線程的全局作用域都擁有這些線程的端口列表,這些列表包括了所有線程使用到的 MessagePort  對(duì)象。在專用線程的情況下,這個(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)。

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

  • 當(dāng)一個(gè)工作線程的文檔對(duì)象列舉不為空的時(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ù)庫(kù)事務(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è)工作線程的文檔對(duì)象列表中的任何一個(gè)對(duì)象都是處于完全活動(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ī)范目前還是處于完善階段,沒(méi)有形成最終的規(guī)范,本文也將上面線程的四種不同狀態(tài)的原文定義附在了后面。

工作線程(Web Worker)API  接口

類庫(kù)和腳本的訪問(wèn)和引入

對(duì)于類庫(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è)操作:

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

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

  3. 如果有任何失敗或者錯(cuò)誤,拋出 SYNTAX_ERR 異常。

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

  5. 對(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)。

  1. 工作導(dǎo)航器對(duì)象(WorkerNavigator)

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

  2. WorkerNavigator 接口定義

    清單 7. WorkerNavigator 接口定義代碼

    1. interface WorkerNavigator {};  

    2.  WorkerNavigator implements NavigatorID;  

    3.  WorkerNavigator implements NavigatorOnLine; 

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

創(chuàng)建與終止線程

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

1.AbstractWorker 抽象接口

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

此外,該接口還定義了錯(cuò)誤處理的事件處理器  onerror,當(dāng)工作線程在通信過(guò)程中遇到錯(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  對(duì)象與之相關(guān)聯(lián)。這個(gè)端口隨著線程的創(chuàng)建而被創(chuàng)建出來(lái),但并沒(méi)有暴露給用戶。所有的基于這個(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è)共享線程。

工作線程位置屬性

工作線程被創(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ī)名稱(hostname),端口(port),路徑名稱(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 &hellip; </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)建以及銷毀都要消耗很多的系統(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_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  }
  • 清單 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();   }

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

看完上述內(nèi)容,你們掌握HTML5中如何進(jìn)行多線程編程應(yīng)用的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI