目前 JavaScript 缺少一个真正可用的多线程并发编程模型。当然 SharedArrayBuffer 和 Atomics 标准的制定也有一段时间了,但是:
SharedArrayBuffer - JavaScript | MDN
一个通用的、固定长度的原始二进制数据缓冲区,类似于ArrayBuffer对象,它们都可以用来在共享内存(shared memory)上创建视图。与ArrayBuffer不同的是,SharedArrayBuffer不能被转移。
为了将一个SharedArrayBuffer对象从一个用户代理共享到另一个用户代理(另一个页面的主进程或当前页面的一个worker),从而实现共享内存,我们需要运用postMessage和结构化克隆算法。
结构化克隆算法
var sab = new SharedArrayBuffer(1024);
worker.postMessage(sab);
这两个SharedArrayBuffer对象指向的共享数据块其实是同一个,所以某一代理对数据块的修改在另一个代理中可见。
共享内存能被同时创建和更新于worker线程或主线程。依赖于系统(CPU、操作系统、浏览器),变化传递给所有上下文环境需要一段时间,因此需要通过原子操作来进行同步。
多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。
Atomics - JavaScript | MDN
使用Atomics提供的基础API,可以实现锁和信号量的同步原语。
锁的定义源码:
https://github.com/mozilla-spidermonkey/js-lock-and-condition
使用锁的案例:
GitHub - rogeryi/offscreen_canvas_demo: Offscreen Canvas Demo
不使用多线程跑不动 40 帧,使用多线程可以跑到 55 帧左右,性能提升了 30% 左右。
Web 技术继续向前演进的其中一个使命: 让大型游戏引擎和 3A 游戏在 Web 上运行,在技术上成为可能。 WebAssembly,OffscreenCanvas,SharedArrayBuffer 等技术的发展使其逐渐成为可能。
WebAssembly:大型游戏引擎一般使用 C/C++ 编写,游戏的业务和控制逻辑使用脚本语言或者动态语言,WebAssembly 可以将引擎或者引擎核心的代码编译成可以在 Web 上运行的 Binary Code,引擎包裹层和游戏业务逻辑可以使用 JavaScript 编写;
SharedArrayBuffer 和 Atomics 为 WASM 提供了线程间共享内存和同步原语,为需要使用多线程并发的游戏引擎运行提供了相应的机制;
OffscreenCanvas 使得游戏引擎可以运行在 Worker 线程,不会阻塞主线程,也不会被主线程阻塞,可以使用线程同步原语实现多线程并发(主线程是不允许使用 wait 的),同时 OffscreenCanvas 使得 Canvas 的更新不会被 DOM 更新所阻塞,提供了更高性能的 Canvas 2D/3D 渲染流水线;