Quantcast
Channel: 英特尔开发人员专区文章
Viewing all articles
Browse latest Browse all 583

谈HTML5之- Web多线程利器WebWorker介绍

$
0
0

内容提要:

  • 什么是WebWorkers
  • 为什么要有WebWorkers
  • WebWorkers的能力
  • 怎么使用WebWorkers
    • Dedicated Worker
    • Shared Worker
  • 开发者工具支持
  • 展望

正文:

Web Workers[1]是W3C和WHATWG定义的Web多线程标准, 是HTML5平台的重要组成部分。使用Web Workers API,HTML5程序员可以构建多线程化的应用,对于提升原有应用的性能,改善用户体验有非常重要的意义。应用级的多线程接口在Android,iOS,Windows,QT等平台上都是非常重要的一部分,从这个意义上看,Web Workers也是HTML5平台化的重要一环。

Web Workers在W3C的演进,在2012年5月已经到了Candidate Recommendation阶段,得到主流浏览器的广泛支持,详见这里

为什么要有Web Workers?在笔者看来,主要有几个原因:

  1. 这是个多核的世界。服务器端自不用说,从个人电脑,到手机平板,多核心已经是主流。Web上提供多线程编程接口,才能通过在应用层对任务细分,充分利用硬件的能力。
  2. Web / JavaScript本身是单线程的。JavaScript本身只提供单线程的执行环境,没有多线程的语义。Web从出生开始,也默认很多工作都要在一个UI线程中完成,这包括页面解析,DOM的操作,CSS的计算,布局,页面渲染等业务逻辑,UI线程非常繁忙,导致性能差,不流畅。随着浏览器的演进,在引擎层面上,Web 引擎提供了多线程的页面解析,多线程渲染加速,Javascript引擎也提供了多线程编译,多线程垃圾回收等机制,这些将一部分业务从UI线程中剥离,通过并行来加速。在应用层面上,也提供异步的编程接口,例如异步xmlhttprequest, 以及setTimeout, setInterval等接口,尽可能的使UI线程不被阻塞。但这些还是不够,随着HTML5应用越来越复杂,出现越来越多的将应用的业务逻辑也多线程化的需求。例如:
    1. )在worker线程对用户的输入做拼写检查,从而不影响UI线程的响应性。
    2. )将任务分配到多个线程中,并行计算,加速渲染,例如分形算法。
    3. )分析audio或者video数据等密集计算。
    4. )本地的Web database的更新。
    5. )应用的资源预取和缓存。

Web Workers API定义了两种worker,一种是Dedicated Worker, 另外一种是Shared Worker。Dedicated Worker只能在他被创建的Context中访问。而Shared Worker一经创建,可以在同一个域里面的多个Context共享访问,例如不同的frame,不同的page。

Web Workers的能力

原则上,worker不具备任何直接影响页面的能力,例如DOM操作。而是需要通过postMessage通知UI线程,间接的影响页面。这种设计的思想和主流的多线程UI设计思想也是一致的。Worker具备的能力:

  • navigator对象
  • XMLHttpRequest
  • setTimeout()/clearTimeout() and setInterval()/clearInterval()
  • importScripts()
  • performance对象

Worker不具备的能力:

  • DOM
  • window对象
  • document对象

通过Chrome开发者工具,可以查看Worker具备哪些能力。

接下来将介绍这两种worker的主要API。

  Dedicated Worker

如何创建一个Dedicated Worker

var worker = new Worker(“workerScript.js”);

上述语句会启动一个新的线程,下载workerScript.js并执行,注意,此处将是一个全新的执行环境,和创建worker的环境完全独立。

如何和一个Dedicated Worker通信

在UI线程向worker发送消息:

worker.postMessage(any message, optional sequence<Transferable> transfer);

这里会将message对象发送给worker线程,如果第二个参数transfer为空,message对象将会被序列化,而后传递到worker线程,worker线程反序列化,构造出message对象。这个过程是完全的拷贝。

第二个参数transfer用来指明哪些要避免拷贝,而是要transfer所有权到另外一个线程。以transfer列表包含Transferrable Object obj为例,这意味着message中的obj要transfer到worker线程;那么postMessage之后,UI线程的JavaScript执行环境将不再拥有obj,而Worker线程会拥有obj,这类似C++ STL里面的auto_ptr。ArrayBuffer是一种常见的可传递对象,可以支持大数据在线程之间传递,从而避免了拷贝。目前所支持的Transferrable Object见这里

关于transfer列表,除了以上之外,2015年刚刚开始提案的新标准SharedArrayBuffer里面定义的SharedArrayBuffer这个类型,可以在多个线程之间共享,这类似于C++ STL里面的shared_ptr。

在worker线程接收消息:

  onmessage = function(message) {

    …

}

在workerScript.js里实现onmessage函数,这里用来接收从主线程发过来的消息。

如何从Dedicated Worker线程发送消息到UI线程

在worker的执行环境里

postMessage(any message, optional sequence<Transferable> transfer);

语义同从UI线程向worker线程传递消息。

在UI线程里接收消息

worker.onmessage = function (message) {

  …

}

注册onmessage回调函数到worker对象,可以接收worker线程传递过来的消息。

如何停止worker线程的执行

如果在UI线程停止worker运行

worker.terminate();

worker线程在隐式地收到terminate消息后,消息队列里的未处理的消息会被丢掉,不再被onmessage函数处理。

如果在worker线程停止运行,则可以调用方法:

close();

close被调用后,所有已经在worker线程的消息队列里的消息都会被丢掉,不再被onmessage处理。

错误处理

worker.onerror = function(error) {

  …

}

在UI线程,注册onerror回调函数到worker对象,那么worker线程在有runtime error发生的时候,会发error事件给主线程。

示例

下面是一个简单的例子,用来描述Dedicated Worker的使用。主线程创建worker线程,worker用来计算素数,并且返回给UI线程做显示。

  Shared Worker

如何创建一个Shared Worker

  var worker = new SharedWorker(“service.js”);

上述语义,在同一个origin范围内,如果尚未存在以service.js创建的shared worker,则会创建新的线程,下载service.js,在一个新的执行环境中运行,并且在UI线程返回SharedWorker对象,这个对象会带一个MessagePort[2],用于和shared worker通信。

如果已经有以service.js创建的shared worker,则不再创建新的线程,而是在UI线程返回一个SharedWorker对象,可以和shared worker通信。此处是以SharedWorker构造函数的参数作为区分,不同的URL创建不同的shared worker线程,相同则共享同一个shared worker。另外,SharedWorker只能在与它相同的origin的执行环境中被访问。

UI 线程如何向Shared Worker发消息

  SharedWorker构造函数返回的对象会带一个MessagePort,用来和worker通信。

首先,建立和SharedWorker的连接,在 UI 线程上worker的onmessage函数注册,或者显示的调用MessagePort的start方法。

  worker.port.onmessage = function cb(…) { }

  或者

  worker.port.addEventListener(function cb(…){ });

  worker.port.start();

  这样,SharedWorker线程的onconnect回调函数会被调用,这个回调函数的参数中可以拿到对应UI线程对应的MessagePort,然后再在这个port上注册onmessage回调函数,就准备好从UI 线程上接收消息了。例如:

  onconnect = function(e) {

    var port = e.ports[0];

    port.onmessage = function()…

}

再然后,UI线程上可以用postMessage方法向SharedWorker发消息,这里和Dedicated Worker的语义是一样的,不同的是,Transferable Object在Shared Worker里并不一定被支持。

Shared Worker如何向UI 线程发消息

  接上,在SharedWorker在onconnect的回调函数里拿到MessagePort之后,就可以通过这个port和对应的UI线程里的对象通信了。

  port.postMessage(…);

如何停止Shared Worker线程的执行

Shared Worker线程可以通过调用close方法停止worker。

和Dedicated Worker有所不同,UI线程里的shared worker对象并没有terminate方法。

错误处理

  同Dedicated worker

示例

这个示例演示了Shared worker和UI线程连接之后,shared worker会向UI线程发送‘Hello World‘消息。

开发者工具支持

Dedicated Worker由于总是和创建它的执行环境关联,所以在页面调试的Source面板同样可以进行worker调试。如下图,选择要调试的线程,并且给该线程的JavaScript代码添加断点。例如:

而对于Shared Worker的调试,则要在独立的调试环境下,具体步骤如下:

  1. Chrome地址栏输入chrome://inspect并回车。
  2. 在inspect页面左侧列表选中Shared Workers,然后点击inspect链接

  3. 然后会跳出新的Chrome开发者工具窗口,可以进行shared worker调试。如下图所示:
     

展望

W3C Web Workers标准,在应用层面线程化,利用硬件的多核能力提高性能,改善用户体验起到非常重要的作用。但由于JavaScript语言天然地缺乏线程语义,编程接口也只能设计为消息传递,而非变量共享。随着HTML5的发展,有越来越多的场景需要在UI线程以及多个worker线程之间共享数据,为此,业界也开始提出新的标准提案SharedArrayBuffer[3],在这个新提案中不仅增加了共享的数据类型,而且在此之上定义了原子操作的语义和线程同步的语义,进一步缩小了HTML5平台和传统原生应用在多线程能力上的差距。

 

更多相关内容,请参考

[1] https://www.w3.org/TR/workers/
[2] https://www.w3.org/TR/2011/WD-webmessaging-20110317/#message-ports
[3] https://tc39.github.io/ecmascript_sharedmem/shmem.html

 

作者简介:

邓攀

资深码农,英特尔亚太研发有限公司Web技术和优化中心,长期耕耘于Web领地

 


Viewing all articles
Browse latest Browse all 583

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>