JavaScript是单线程的,又是异步的,而最新的HTML5中,通过Web Workers可以在JS中支持多线程开发。这是几个意思?异步还是单线程,这怎么理解?Web Workers又是什么原理?实际开发中,异步和多线程之间如何交互?答案就在下面。主要涉及的内容有:

  • 为什么异步解决不了问题

  • Worker又是什么玩法

  • Cesium中的异步+多线程框架

为什么异步解决不了问题

简单说,JavaScript是单线程的,简单易用,但如果遇到时间较长的任务时,则容易出现卡死的现象,为了避免这种问题,我们对时间久的任务采用异步的方式,保证页面的快速响应。

比如我们常见的setTimeout,指定某个时间运行,然后在指定时间运行该函数。然而“JS运行在单线程环境中,定时器仅仅是计划代码在未来某个时间执行,并不作为保证执行时间,因为不同时间可能有其他代码在控制JS进程,而所有函数必须使用相同的线程执行。实际上,由浏览器负责排序,指派某段代码在某个时间点运行的优先级”。在这里,单线程,异步又该如何理解?这就需要我们了解一下异步的原理。

摘自《Secrets of theJavaScript Ninja》

这个图初看有点晦涩,沉下心来好好看一遍,然后在看看这段文字解释,相信你会大有收获。首先,右侧是JS引擎所触发的代码,左侧是事件队列,0,10,20则是自上而下的时间轴,我们就以毫秒为单位吧。

首先,在2ms处,执行了setTimeout语句,设定10ms后执行fun1函数;在5ms处出现了鼠标点击事件,执行fun2函数;接着在10ms处出执行了setInterval,设定10ms后执行fun3函数。而整个JS代码块执行大约用了18ms。因此,首先当鼠标点击后的回调时间fun2以及setTimeout所触发的fun1函数发现,此时JS代码块还控制着执行进行,则两者都进入队列,等待一个合适的时机在运行

这时,在18ms处,JS代码块终于运行完了,机会来了,这时鼠标的callback回调关联着一个异步事件(因为我们无法知道用户想要何时点击鼠标,所以我们认为回调事件是异步的),所以很不幸,fun1事件还是要继续呆在队列中。同时,在20ms出,触发了第一次setInterval,当然一视同仁,所以fun3也进入队列。

28ms处,终于鼠标回调事件结束了,看看队列里面,setTimeout的fun1函数终于有了出头日,开始执行fun1函数,队列中仅剩下setInterval的fun3函数。在30ms时,setInterval又调用了一次,但发现队列中上一次的函数还未运行,所以这一次的触发没有任何效果,丢弃掉。

终于36ms后,Time触发的fun1运行完毕,队列中仅剩的fun3函数开始运行,在40ms时,setInterval再次周期触发,但此时js进程还是由fun3函数控制,所以触发事件进入队列。

以此类推,一直运行到队列为空时,这样一旦有事件触发,则会直接运行。 希望所有人能认真理解这个过程,并发现setTimeout和setInterval在处理上的相同和不同处,这块不是本文重点,所以不多讨论。

通过这样一个过程,相信大家理解了异步和单线程之间的关系:JS在一个线程中运行,但通过消息队列来实现异步调用,但调用本身也是在同一个线程中运行,只是可以延后或分解任务。举个不太妥当的例子:假如只有一个出租车司机,相当于JS的进程,模拟一个线程的情况,而乘客相当于异步请求,通过滴滴打车,可以约定某个时间来接你,然后到达目的地(函数实现)。但触发并不等同于运行,乘客下单时,司机还在载其他客人,但答应在约定时间接你。这时他载完该乘客后立马去接你,满足你的请求。而在此之前,各自忙各自的,他在执行他的任务,你有可能在等,或者在刷手机(服务端接收请求,并返回结果)。

异步确实能尽可能的优化,比如Ajax等异步请求。但这要求把任务分解的比较简单,在时间比较久的任务下还是会出现无响应的问题,不管你的进度条做的有多好看。

Worker又能干什么事情

异步只是看上去更及时而已,但该花的时间一点也不会少,而且因为调度本身的成本,时间还会多花一点。而且,随着Web应用的不断发展 ,在JS端要求的计算量也越来越大,这种时候,Web Worker可以让JS在后台解决这些问题,而不必担心影响用户体验。

需要注意的是,Worker线程完全在另一个作用域中,而且无法操作DOM元素,不能与网页代码共享作用域。但这已经足够了,比如排序,或者zip压缩等操作,都可以放到Worker线程来运行,从而能够在Web端进行类似CS的很多应用。

Worker的具体使用这里也不介绍,主要解释一下下面这张图:

摘自AlloyTeam团队《深入理解Web Worker》

main.js中,在创建woker线程后,立即调用了postMessage方法传递了数据,在worker线程还没创建完成时,main.js中发出的消息,会先存储在一个临时消息队列中,当异步创建worker线程完成,临时消息队列中的消息数据复制到woker对应的WorkerRunLoop的消息队列中,worker线程开始处理消息。在经过一轮消息来回后,继续通信时, 这个时候因为worker线程已经创建,所以消息会直接添加到WorkerRunLoop的消息队列中 ---摘自AlloyTeam团队《深入理解Web Worker》

这是Worker线程和主线程的一个交互方式,首先可见消息的发送和接收采用的是postmessage和onmessage,相信做过MFC开发的一看也能发现,这也是一个异步消息队列的传输方式。

在数据传输中,或许在Worker线程中采用同步,效果会更好。另外,在参数的传递是拷贝方式,但同时提供Transferable Objects方式,可以传地址(不是拷贝)并加锁,这是一个非常实用的参数,特别是在比较大的二进制数值运算中。

如果需要在worker脚本中加载其他js文件,则使用importScripts函数,这是一个同步过程,所以性能会有影响,不过既然是在工作者线程中,所以也不太严重。

还有一个问题,在产品化的时候如何混淆压缩这些worker.js脚本,因为我们需要引入它们,所以造成了这部分代码很容易format,让别人下载分析。虽然技术在于分享,毕竟作为产品,这也是需要考虑的部分,总不能直接源码提供吧。我看到Google WebGL Earth上有一个方式,采用Blob的思路内嵌Worker。因为我还没用过,这里也不多说了,只提供这样一个思路。

Cesium中的异步+多线程框架

说了这么多,下面和大家分享一下Cesium中多线程设计的框架吧,我觉得很专业,但也有些复杂,但复杂的同时带来了很好的扩展性。简单来说就是一个插件的思路。

Cesium中设计到三维球的很多计算,数据量很大,比如地形的三角网,以及参数化的Geometry中vbo的计算,而这些都是在Worker中实现的,参数的传递,不同类型之间的算法也不同,所以设计一个易用且易扩展的Worker框架则显得非常有必要。

如上图,用户只需要创建一个TaskProcessor,指定具体需要创建线程的类型,比如(圆,面,还是线),然后调用scheduleTask,里面是该对象的具体参数,比如圆就是圆心+半径,这样便完成了调用过程。那返回结果怎么接受呢?大家注意最后一行返回的参数Promise,这也是一个Promise的异步方式,用户自然能够方便的获取到结果。下面是返回结果的实现。

当然使用的简单,多数意味着实现的复杂。这里主要和大家说一下用户指定Worker的名字,如果根据名字创建该Worker线程,并且易于扩展,也就是插件的实现思路。

首先,有一个cesiumWorkerBootstrapper的Worker,所有createWorker都会建立一个cesiumWorkerBootstrapper线程,只是赋予不同的参数(name不同)。

而在cesiumWorkerBootstrapper线程中,使用了requirejs,根据指定的路径和文件名,获取对应的函数,同时替换的onmessage函数。

此时,主线程在调用scheduleTask时,会再次发送postmessage,并传入参数,而此时requirejs已经找到了对应的功能函数。,即替换onmessage的函数。

而这些函数都是由createTaskProcessorWorker封装的匿名函数,类似于回调函数,进而实现对应的功能。并且返回指定结果。

这样,一个多线程设计框架就完成了,并且通过Promise机制,方便用户的使用,而内部使用require.js,实现了插件的这样一个方式。这块代码涉及的内容比较多,这里也是理解思路,具体的细节还是需要代码的调试才能更好的理解,这里也仅仅提供参考。

Cesium原理篇:4Web Workers剖析的更多相关文章

  1. Cesium原理篇:3最长的一帧之地形(2:高度图)

           这一篇,接着上一篇,内容集中在高度图方式构建地球网格的细节方面.        此时,Globe对每一个切片(GlobeSurfaceTile)创建对应的TileTerrain类,用来维 ...

  2. Cesium原理篇:7最长的一帧之Entity(下)

    上一篇,我们介绍了当我们添加一个Entity时,通过Graphics封装其对应参数,通过EntityCollection.Add方法,将EntityCollection的Entity传递到DataSo ...

  3. Cesium原理篇:5最长的一帧之影像

    如果把地球比做一个人,地形就相当于这个人的骨骼,而影像就相当于这个人的外表了.之前的几个系列,我们全面的介绍了Cesium的地形内容,详见: Cesium原理篇:1最长的一帧之渲染调度 Cesium原 ...

  4. Cesium原理篇:4Web Workers剖析(2)

    What's the WebWorkers? 2008 年 W3C 制定出第一个 HTML5 草案中提出了工作线程(Web Worker)的概念,并且规范出 Web Worker 的三大主要特征:能够 ...

  5. Cesium原理篇:3最长的一帧之地形(3:STK)

    有了之前高度图的基础,再介绍STK的地形相对轻松一些.STK的地形是TIN三角网的,基于特征值,坦白说,相比STK而言,高度图属于淘汰技术,但高度图对数据的要求相对简单,而且支持实时构建网格,STK具 ...

  6. Cesium原理篇:3D Tiles(1)渲染调度

    Cesium在2016年3月份左右推出3D Tiles数据规范,在glTF基础上提供了LOD能力,定位就是Web环境下海量三维模型数据.虽然目前3D Tiles还是Beta阶段,有不少硬伤,但3D T ...

  7. Cesium原理篇:3D Tiles(2)数据结构

    上一节介绍3D Tiles渲染调度的时候,我们提到目前Cesium支持的Cesium3DTileContent目前支持如下类型: Batched3DModel3DTileContent Instanc ...

  8. Cesium原理篇:3D Tiles(3)个人总结

    个人结论:目前,在演示层面,3D Tiles问题不大,但项目应用上就不够成熟了,所以问问自己,你是想吃瓜呢还是想吃螃蟹? 好的方面 数据规范 我非常喜欢glTF的整体设计,概括有四点:第一,数据块(B ...

  9. Cesium原理篇:3D Tiles(1)渲染调度【转】

    Cesium在2016年3月份左右推出3D Tiles数据规范,在glTF基础上提供了LOD能力,定位就是Web环境下海量三维模型数据.虽然目前3D Tiles还是Beta阶段,有不少硬伤,但3D T ...

随机推荐

  1. SMA、SMB、SMC封装的二极管

    以常见的贴片肖特基二极管SS14 SS24 SS34为例,三种管子区别主要在电流上,有三种封装:SMA.SMB.SMC. 从成本和体积来说,优先选用最小尺寸的SMA/DO-214AC封装,其他封装一般 ...

  2. 热门的PHP框架

    每个PHP框架都拥有各自独特的地方.同时PHP语言已经获得了巨大的认同并且成为了世界上最通用的服务器脚本语言.PHP也俨然成为了最容易学习的web动态开发语言.在PHP发展的同时,PHP框架也迅速崛起 ...

  3. nodejs 模块恩仇录

    anywhere 一句话:随时随地将你的当前目录变成一个静态文件服务器的根目录. 安装 npm install anywhere -g anywhere -h localhost -p 8060 fa ...

  4. 初涉深度优先搜索--Java学习笔记(二)

    版权声明: 本文由Faye_Zuo发布于http://www.cnblogs.com/zuofeiyi/, 本文可以被全部的转载或者部分使用,但请注明出处. 上周学习了数组和链表,有点基础了解以后,这 ...

  5. jvm的内存分配总结

    最近看了周志明版本的<深入理解Java虚拟机>第一版和第二版,写的很好,收获很多,此处总结一下.   jvm中内存划分:   如上图,一共分为五块,其中: 线程共享区域为: 1.java堆 ...

  6. C语言中的插入排序(2016-12-30)

    直接插入排序: 算法思想:假设待排序的记录存放在数组R[1--n]中,初始时,i=1,R[1]自成一个有序区,无序区为R[2--n].然后从i=2起直到i=n,依次将R[i]插入当前的有序区R[1.. ...

  7. Quartz 2D在ios中的使用简述二:创建画布

    在iOS中使用Quartz画图时,第一步就是要获取画布(图形上下文),然后再画布上做各种操作.先看下CoreGraphics.h这个头文件,就可以知道能够创建多少种上下文类型. #include &l ...

  8. AngularJS SPA Template For Visual Studio

    单页面应用程序(SPA)[使用JavaScript.CSS和HTML强大的功能,可以构建一个单页面应用程序(SPAs)],它提供了丰富的用户体验页面.导航技术和AJAX提供必要的功能,而不用重新加载页 ...

  9. 编译可在Nexus5上运行的CyanogenMod13.0 ROM(基于Android6.0)

    编译可在Nexus5上运行的CyanogenMod13.0 ROM (基于Android6.0) 作者:寻禹@阿里聚安全 前言 下文中无特殊说明时CM代表CyanogenMod的缩写. 下文中说的“设 ...

  10. 剑指Offer面试题:20.栈的压入、弹出序列

    一.题目:栈的压入.弹出序列 题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1.2.3.4.5是某栈的压栈序列,序列4 ...