# 通过Worker异步计算优化CPU使用
更新时间 2025-02-25
# 为什么需要降低JS线程消耗
TapTap 小游戏在平台侧是运行在JS线程中的,JS代码不仅需要处理业务逻辑,也需要执行渲染指令,在一个需要60帧运行的游戏上,每一帧的耗时不能超过16毫秒,而这其中渲染耗时至少需要6毫秒,那么处理业务逻辑的JS代码需要控制在10毫秒以内。
然而在实际运行过程中,很难保证JS的代码逻辑可以稳定控制在10毫秒以内。
# 分帧策略
对于计算性能造成游戏帧率下降或卡顿的场景,大多数开发人员会选择分帧策略,将执行时间较长的函数拆分成多个执行时间短的子任务,将子任务分配到每一帧中,按计划顺序执行,直到全部子任务执行完毕。分帧策略虽然对部分场景有效果,但是依旧存在如下问题:
- 不是所有计算逻辑都能够被拆分。比如数组排序, 树的递归查找, 图像处理算法等, 执行中需要维护当前状态, 且调用上非线性, 无法轻易地拆分为子任务。
- 可以拆分的逻辑难以把控力度。拆分的子任务在高性能机器上可以控制在 16ms 内, 但在性能落后的机器上表现并不一定理想。 16ms 的用户感知时间, 并不会因为用户手上机器的差别而变化。
- 拆分的子任务并不稳定。计算逻辑可能会随着业务场景发生变化,将同步计算逻辑拆分成子任务,可能会造成每次改动业务都需要review多个子任务的代码。
这个时候,可以使用Worker 的多线程能力, 从宏观上将整个同步 JS 任务异步化。
# 多线程 Worker
详细接口文档参考:Worker API
一些异步处理的任务,可以放置于 Worker 线程中运行,待运行结束后,再把结果返回到小游戏主线程。
Worker 线程运行于一个单独的全局上下文与线程中,会全局暴露一个 worker 对象。
Worker 线程与主线程之间的数据传输,双方使用 Worker.postMessage() 来发送数据,Worker.onMessage() 来接收数据,传输的数据并不是直接共享,而是被复制的。
# 注意事项
- Worker 最大并发数量限制为 1 个,创建下一个前请用 Worker.terminate() 结束当前 Worker。
- Worker 内代码只能 require 配置的 Worker 路径内的文件,无法引用其它路径文件。
- Worker 内不支持globalThis、window,返回值均为 null。
- Worker 线程内支持 console 能力,目前仅支持 info、log、debug、warn、error。
- 可以在 Worker 中使用 WebAssembly 相关能力。
- 可以在 worker 中使用定时器相关 API:setTimeout、clearTimeout、setInterval、clearInterval。
# 接口差异
小游戏环境下的 Worker 能力与主线程存在一定的差异,具体对比如下:
能力 | Worker | 主线程 |
---|---|---|
渲染能力 | 不支持 | 支持 |
JS(tap)API | 不支持 | 支持 |
网络/IO | 不支持 | 支持 |
定时器 | 支持 | 支持 |
WebAssembly | 支持 | 支持 |