Shared Memory and Atomics

简介

ECMAScript 2017 引入了一个新的构造函数 SharedArrayBuffer 和 具有辅助函数的命名空间对象 Atomics

语法

SharedArrayBuffer

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

// 主线程
const w = new Worker('worker.js')

w.postMessage('hello')

w.onmessage = function (ev) {
  console.log(ev.data)
}
// worker.js
onmessage = function (ev) {
  console.log(ev.data)
  postMessage('hi')
}

上面代码中,主线程创建了一个 Woker 线程,该线程与主线程之间通过 postMessage() 通信。

线程之间的数据交换可以是各种格式,这种数据交换采用复制机制,即一个进程将要分享的数据复制一份,通过 postMessage 方法发送给另一个进程。如果数据量较大,通信的效率会明显较低。共享内存的方式,会大大提高效率。

SharedArrayBuffer 对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,唯一的区别是后者无法共享数据。

new SharedArrayBuffer(length)

参数

length:所创建的数组缓冲区的大小,以字节(byte)为单位。

返回值

一个大小指定的新 SharedArrayBuffer 对象。其内容被初始化为 0。

示例

// 主线程

// 新建 1KB 共享内存
const sharedBuffer = new SharedArrayBuffer(1024)

// 主线程将共享内存的地址发送出去
w.postMessage(sharedBuffer)

// 在共享内存上建立视图,供写入数据
const sharedArray = new Int32Array(sharedBuffer)
// Worker 线程
onmessage = function (ev) {
  // 主线程共享的数据,就是 1KB 的共享内存
  const sharedBuffer = ev.data

  // 在共享内存上建立视图,方便读写
  const sharedArray = new Int32Array(sharedBuffer)

  // ...
}

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

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

Atomics

多线程共享内存,最大的问题就是防止两个线程同时修改某个地址,或者说,当一个线程修改共享内存以后,必须有一个机制让其他线程同步。SharedArrayBuffer API 提供 Atomics 对象,保证所有共享内存的操作都是“原子性”的,并且可以在所有线程内同步。

原子操作:多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。

与一般的全局对象不同,Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象一样)。

方法

Atomics.store(),Atomics.load()

store() 方法用来向共享内存写入数据,并返回该值。 load() 方法用来从共享内存读出数据。

Atomics.store(typedArray, index, value)
Atomics.load(typedArray, index)

typedArray

一个指定类型的 shared 数组. 类型为 Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, 或者 Uint32Array 其中一个。

index

typedArray 中用来存储 value 的位置。

value

要存储的数字。

var sab = new SharedArrayBuffer(1024)
var ta = new Uint8Array(sab)

Atomics.store(ta, 0, 12) // 12
Atomics.load(ta, 0) // 12

Atomics.exchange()

将数组中指定的元素更新为给定的值,并返回该元素更新前的值。

Atomics.exchange(typedArray, index, value)

var sab = new SharedArrayBuffer(1024)
var ta = new Uint8Array(sab)

Atomics.exchange(ta, 0, 12) // returns 0, the old value
Atomics.load(ta, 0) // 12

Atomics.wait(),Atomics.notify()

这两个方法相当于锁内存,即在一个线程进行操作时,让其他线程休眠(建立锁),等到操作结束,再唤醒那些休眠的线程(解除锁)。

Atomics.wait(typedArray, index, value[, timeout])

typedArray

一个共享的 Int32Array

index

typedArray 中要唤醒的目标索引。

value

给定需要检测的位置索引的预期值。

timeout

可选,超时前等待的毫秒数。 Infinity, 如未提供该参数,将为无穷大。

Atomics.notify(typedArray, index, count)

count

要通知的正在休眠的代理的数量。默认是 +Infinity

返回值

一个 String 字符串,值为 "ok", "not-equal", 或 "timed-out" 三种之一。

如果 sharedArray[index] 不等于 value,就返回字符串 not-equal,否则就进入休眠。如果 Atomics.notify() 方法唤醒,就返回字符串 ok;如果因为超时唤醒,就返回字符串 timed-out

// Worker 线程
self.addEventListener(
  'message',
  (event) => {
    const sharedArray = new Int32Array(event.data)
    const arrayIndex = 0
    const expectedStoredValue = 50
    Atomics.wait(sharedArray, arrayIndex, expectedStoredValue)
    console.log(Atomics.load(sharedArray, arrayIndex))
  },
  false,
)

上面代码中,Atomics.wait() 方法等同于告诉 Worker 线程,只要满足给定条件(sharedArray0 号位置等于 50),就在这一行 Worker 线程进入休眠。

// 主线程
const newArrayValue = 100
Atomics.store(sharedArray, 0, newArrayValue)
const arrayIndex = 0
const queuePos = 1
Atomics.notify(sharedArray, arrayIndex, queuePos)

上面代码中,sharedArray 的 0 号位置改为 100,然后就执行 Atomics.notify()方法,唤醒在 sharedArray 的 0 号位置休眠队列里的一个线程。

注意,浏览器的主线程不宜设置休眠,这会导致用户失去响应。而且,主线程实际上会拒绝进入休眠。

Atomics.add()

将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。

Atomics.add(typedArray, index, value)

var sab = new SharedArrayBuffer(1024)
var ta = new Uint8Array(sab)

Atomics.add(ta, 0, 12) // returns 0, the old value
Atomics.load(ta, 0) // 12
Atomics.and()

将指定位置上的数组元素与给定的值相与,并返回与操作前该元素的值。

Atomics.and(typedArray, index, value)

var sab = new SharedArrayBuffer(1024)
var ta = new Uint8Array(sab)
ta[0] = 5

Atomics.and(ta, 0, 1) // returns 0, the old value
Atomics.load(ta, 0) // 1
Atomics.compareExchange()

如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。

Atomics.compareExchange(typedArray, index, expectedValue, replacementValue)

expectedValue

用于比较的值。

replacementValue

将要替换上的值。

var sab = new SharedArrayBuffer(1024)
var ta = new Uint8Array(sab)
ta[0] = 7

Atomics.compareExchange(ta, 0, 7, 12) // returns 7, the old value
Atomics.load(ta, 0) // 12
Atomics.isLockFree()

返回一个布尔值,表示 Atomics 对象是否可以处理某个 size 的内存锁定。如果返回 false,应用程序就需要自己来实现锁定。

Atomics.isLockFree(size)

Atomics.isLockFree(1) // true
Atomics.isLockFree(2) // true
Atomics.isLockFree(3) // false
Atomics.isLockFree(4) // true
Atomics.isLockFree(5) // false
Atomics.isLockFree(6) // false
Atomics.isLockFree(7) // false
Atomics.isLockFree(8) // false
Atomics.isLockFree(Float64Array.BYTES_PER_ELEMENT) // false,Atomics方法无法处理Float64Array
Atomics.or()

将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值。

Atomics.or(typedArray, index, value)

var sab = new SharedArrayBuffer(1024)
var ta = new Uint8Array(sab)
ta[0] = 2

Atomics.or(ta, 0, 1) // returns 2, the old value
Atomics.load(ta, 0) // 3
Atomics.sub()

将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。

Atomics.sub(typedArray, index, value)

const sab = new SharedArrayBuffer(1024)
const ta = new Uint8Array(sab)
ta[0] = 48

Atomics.sub(ta, 0, 12) // returns 48, the old value
Atomics.load(ta, 0) // 36
Atomics.xor()

将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值。

Atomics.xor(typedArray, index, value)

const sab = new SharedArrayBuffer(1024)
const ta = new Uint8Array(sab)
ta[0] = 5

Atomics.xor(ta, 0, 1) // returns 5, the old value
Atomics.load(ta, 0) // 4
Copyright © 零度实验室 2020 all right reserved,powered by Gitbook修订时间: 2021-07-02 10:46:52

results matching ""

    No results matching ""