Cesium原理篇:4Web Workers剖析

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

  • 为何异步解决不了问题浏览器

  • Worker又是什么玩法多线程

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

为何异步解决不了问题

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

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

 

image

摘自《Secrets of theJavaScript Ninja》 异步

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

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

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

       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的具体使用这里也不介绍,主要解释一下下面这张图:

 

image

摘自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方式,能够传地址(不是拷贝)并加锁,这是一个很是实用的参数,特别是在比较大的二进制数值运算中。

 

image

 

       若是须要在worker脚本中加载其余js文件,则使用importScripts函数,这是一个同步过程,因此性能会有影响,不过既然是在工做者线程中,因此也不太严重。

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

Cesium中的异步+多线程框架

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

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

 

image

 

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

 

image

 

       固然使用的简单,多数意味着实现的复杂。这里主要和你们说一下用户指定Worker的名字,若是根据名字建立该Worker线程,而且易于扩展,也就是插件的实现思路。

       首先,有一个cesiumWorkerBootstrapper的Worker,全部createWorker都会创建一个cesiumWorkerBootstrapper线程,只是赋予不一样的参数(name不一样)。

 

image

 

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

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

 

image

 

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

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

 

image

相关文章
相关标签/搜索