SharedArrayBuffer

JavaScript 是单线程的,Web worker 引入了多线程:主线程用来与用户互动,Worker 线程用来承担计算任务。每个线程的数据都是隔离的,通过postMessage()通信。下面是一个例子。

  1. // 主线程
  2. const w = new Worker('myworker.js');

上面代码中,主线程新建了一个 Worker 线程。该线程与主线程之间会有一个通信渠道,主线程通过w.postMessage向 Worker 线程发消息,同时通过message事件监听 Worker 线程的回应。

  1. // 主线程
  2. w.postMessage('hi');
  3. w.onmessage = function (ev) {
  4. console.log(ev.data);
  5. }

上面代码中,主线程先发一个消息hi,然后在监听到 Worker 线程的回应后,就将其打印出来。

Worker 线程也是通过监听message事件,来获取主线程发来的消息,并作出反应。

  1. // Worker 线程
  2. onmessage = function (ev) {
  3. console.log(ev.data);
  4. postMessage('ho');
  5. }

线程之间的数据交换可以是各种格式,不仅仅是字符串,也可以是二进制数据。这种交换采用的是复制机制,即一个进程将需要分享的数据复制一份,通过postMessage方法交给另一个进程。如果数据量比较大,这种通信的效率显然比较低。很容易想到,这时可以留出一块内存区域,由主线程与 Worker 线程共享,两方都可以读写,那么就会大大提高效率,协作起来也会比较简单(不像postMessage那么麻烦)。

ES2017 引入SharedArrayBuffer,允许 Worker 线程与主线程共享同一块内存。SharedArrayBuffer的 API 与ArrayBuffer一模一样,唯一的区别是后者无法共享数据。

  1. // 主线程
  2. // 新建 1KB 共享内存
  3. const sharedBuffer = new SharedArrayBuffer(1024);
  4. // 主线程将共享内存的地址发送出去
  5. w.postMessage(sharedBuffer);
  6. // 在共享内存上建立视图,供写入数据
  7. const sharedArray = new Int32Array(sharedBuffer);

上面代码中,postMessage方法的参数是SharedArrayBuffer对象。

Worker 线程从事件的data属性上面取到数据。

  1. // Worker 线程
  2. onmessage = function (ev) {
  3. // 主线程共享的数据,就是 1KB 的共享内存
  4. const sharedBuffer = ev.data;
  5. // 在共享内存上建立视图,方便读写
  6. const sharedArray = new Int32Array(sharedBuffer);
  7. // ...
  8. };

共享内存也可以在 Worker 线程创建,发给主线程。

SharedArrayBufferArrayBuffer一样,本身是无法读写的,必须在上面建立视图,然后通过视图读写。

  1. // 分配 10 万个 32 位整数占据的内存空间
  2. const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100000);
  3. // 建立 32 位整数视图
  4. const ia = new Int32Array(sab); // ia.length == 100000
  5. // 新建一个质数生成器
  6. const primes = new PrimeGenerator();
  7. // 将 10 万个质数,写入这段内存空间
  8. for ( let i=0 ; i < ia.length ; i++ )
  9. ia[i] = primes.next();
  10. // 向 Worker 线程发送这段共享内存
  11. w.postMessage(ia);

Worker 线程收到数据后的处理如下。

  1. // Worker 线程
  2. let ia;
  3. onmessage = function (ev) {
  4. ia = ev.data;
  5. console.log(ia.length); // 100000
  6. console.log(ia[37]); // 输出 163,因为这是第38个质数
  7. };