Cesium之Web Workers
1. 引言
多线程是编程中常用的方法,例如,在桌面程序中,主线程一般是UI线程,负责UI绘制与用户交互,而运算处理往往是交给背后的工作线程,这样可以有效避免交互时的卡顿感
浏览器是多进程的,每打开一个网页,都会开启一个渲染进程,渲染进程包含:
- GUI渲染线程 (有且只有一个)
- JS引擎线程 (有且只有一个)
- 事件触发线程
- 定时器触发线程
- 异步http请求线程
其中,JS引擎线程就是解析JS代码的线程,由于JS引擎线程有且只有一个,所以JS代码执行是单线程的(笔者注:异步函数是使用任务队列实现的,除非调用了其他线程的函数,如定时器等,不然异步函数还是单线程执行的)
GUI渲染线程与JS引擎线程是互斥的,且JS引擎线程会先执行,如果JS代码卡住会导致GUI绘制卡住
有关浏览器架构与原理,可以参考:
Web Workers就是创建JS代码执行的线程,使得JS代码执行能以多线程的方式执行,避免JS引擎线程卡住
有关Web Workers的解释可以参考:
本文描述浏览器中的Web Workers并基于Cesium源码进行举例
2. Web Workers
通常而言,Web Workers包含:
- 专用线程(Dedicated Workers)
- 共享线程(Shared Workers)
- 后台线程(Service Workers)等
这里主要是记述通常使用的专用线程(Dedicated Workers)
Web Worker的大致的使用方法如下:
(在主线程里)创建一个Web Worker:
const worker = new Worker('WebWorkerTest.js')
WebWorkerTest.js
是Web Worker执行的JS代码文件- 加载Web Worker执行的JS代码文件需要使用HTTP或HTTPS协议,即,需要搭建网络服务器
(在主线程里)向创建的Web Worker发送数据:
worker.postMessage([2, 3])
(在子线程Web Worker,即WebWorkerTest.js
中)接收主线程的数据、处理并发送给主线程:
onmessage = function(e) { console.log('Message received from main script') const workerResult = e.data[0] * e.data[1] console.log('Posting message back to main script') postMessage(workerResult) }
(在主线程里)接收Web Worker发送的数据:
worker.onmessage = (e) => { console.log("Result:", e.data) }
综上,此处创建了两个文件:WebWorkerTest.js和WebWorkerTest.html
WebWorkerTest.html代码如下:
<body> <script> const worker = new Worker('WebWorkerTest.js') worker.postMessage([2, 3]) worker.onmessage = (e) => { console.log("Result:", e.data) } </script> </body>
WebWorkerTest.js代码如下:
onmessage = function(e) { console.log('Message received from main script') const workerResult = e.data[0] * e.data[1] console.log('Posting message back to main script') postMessage(workerResult) }
运行结果如下(使用VS Code和Live Server插件):
更详细的Web Worker的使用方法,可以参考以下文档:
3. Cesium中的Web Workers
Cesium源码中,对Web Workers进行了封装,封装为TaskProcessor
源码中给出的TaskProcessor使用示例为:
const taskProcessor = new Cesium.TaskProcessor('myWorkerPath'); const promise = taskProcessor.scheduleTask({ someParameter : true, another : 'hello' }); if (!Cesium.defined(promise)) { // too many active tasks - try again later } else { promise.then(function(result) { // use the result of the task }); }
查看源码,可以知道taskProcessor.scheduleTask()
函数为:
TaskProcessor.prototype.scheduleTask = function (parameters, transferableObjects) { // ... this._worker = createWorker(this); return Promise.resolve(canTransferArrayBuffer()).then(function ( canTransferArrayBuffer ) { processor._worker.postMessage( { id: id, parameters: parameters, canTransferArrayBuffer: canTransferArrayBuffer, }, transferableObjects ); return deferred.promise; }); };
createWorker()
函数为
function createWorker(processor) { const worker = new Worker(getBootstrapperUrl()); worker.postMessage = defaultValue( worker.webkitPostMessage, worker.postMessage ); // ... return worker; }
不难看出,Cesium中将Web Workers封装成了Promise
,既有操作Promise
的优雅,又有调用Web Workers
带来的多线程优势
例如,在Scene\Primitive.js
中,使用TaskProcessor创建Geometry
:
先是使用createGeometry.js
的文件名创建TaskProcessor:
if (!defined(createGeometryTaskProcessors)) { createGeometryTaskProcessors = new Array(numberOfCreationWorkers); for (i = 0; i < numberOfCreationWorkers; i++) { createGeometryTaskProcessors[i] = new TaskProcessor("createGeometry"); } }
然后创建promise数组:
promises.push( createGeometryTaskProcessors[i].scheduleTask( { subTasks: subTasks[i], }, subTaskTransferableObjects ) );
最后使用Promise.all
方法执行所有任务并等待结果返回:
Promise.all(promises) .then(function (results) { primitive._createGeometryResults = results; primitive._state = PrimitiveState.CREATED; }) .catch(function (error) { setReady(primitive, frameState, PrimitiveState.FAILED, error); });
4. 参考资料
[1] Web Workers API - Web API 接口参考 | MDN (mozilla.org)
[2] Web Worker 使用教程 - 阮一峰的网络日志 (ruanyifeng.com)
[3] Cesium原理篇:4Web Workers剖析 - fu*k - 博客园 (cnblogs.com)
[4] 浏览器的进程与线程--深入同步、异步问题 - 知乎 (zhihu.com)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律