简介

ES8引入了SharedArrayBuffer和Atomics,通过共享内存来提升workers之间或者worker和主线程之间的消息传递速度。

本文将会详细的讲解SharedArrayBuffer和Atomics的实际应用。

Worker和Shared memory

在nodejs中,引入了worker_threads模块,可以创建Worker. 而在浏览器端,可以通过web workers来使用Worker()来创建新的worker。

这里我们主要关注一下浏览器端web worker的使用。

我们看一个常见的worker和主线程通信的例子,主线程:

var w = new Worker("myworker.js")w.postMessage("hi");     // send "hi" to the worker
w.onmessage = function (ev) {console.log(ev.data);  // prints "ho"
}

myworker的代码:

onmessage = function (ev) {console.log(ev.data);  // prints "hi"postMessage("ho");     // sends "ho" back to the creator
}

我们通过postMessage来发送消息,通过onmessage来监听消息。

消息是拷贝之后,经过序列化之后进行传输的。在解析的时候又会进行反序列化,从而降低了消息传输的效率。

为了解决这个问题,引入了shared memory的概念。

我们可以通过SharedArrayBuffer来创建Shared memory。

考虑下上面的例子,我们可把消息用SharedArrayBuffer封装起来,从而达到内存共享的目的。

//发送消息
var sab = new SharedArrayBuffer(1024);  // 1KiB shared memory
w.postMessage(sab)//接收消息
var sab;
onmessage = function (ev) {sab = ev.data;  // 1KiB shared memory, the same memory as in the parent
}

上面的这个例子中,消息并没有进行序列化或者转换,都使用的是共享内存。

ArrayBuffer和Typed Array

SharedArrayBuffer和ArrayBuffer一样是最底层的实现。为了方便程序员的使用,在SharedArrayBuffer和ArrayBuffer之上,提供了一些特定类型的Array。比如Int8Array,Int32Array等等。

这些Typed Array被称为views。

我们看一个实际的例子,如果我们想在主线程中创建10w个质数,然后在worker中获取这些质数该怎么做呢?

首先看下主线程:

var sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100000); // 100000 primes
var ia = new Int32Array(sab);  // ia.length == 100000
var primes = new PrimeGenerator();
for ( let i=0 ; i < ia.length ; i++ )ia[i] = primes.next();
w.postMessage(ia);

主线程中,我们使用了Int32Array封装了SharedArrayBuffer,然后用PrimeGenerator来生成prime,存储到Int32Array中。

下面是worker的接收:

var ia;
onmessage = function (ev) {ia = ev.data;        // ia.length == 100000console.log(ia[37]); // prints 163, the 38th prime
}

并发的问题和Atomics

上面我们获取到了ia[37]的值。因为是共享的,所以任何能够访问到ia[37]的线程对该值的改变,都可能影响其他线程的读取操作。

比如我们给ia[37]重新赋值为123。虽然这个操作发生了,但是其他线程什么时候能够读取到这个数据是未知的,依赖于CPU的调度等等外部因素。

为了解决这个问题,ES8引入了Atomics,我们可以通过Atomics的store和load功能来修改和监控数据的变化:

console.log(ia[37]);  // Prints 163, the 38th prime
Atomics.store(ia, 37, 123);

我们通过store方法来向Array中写入新的数据。

然后通过load来监听数据的变化:

while (Atomics.load(ia, 37) == 163);
console.log(ia[37]);  // Prints 123

还记得java中的重排序吗?

在java中,虚拟机在不影响程序执行结果的情况下,会对java代码进行优化,甚至是重排序。最终导致在多线程并发环境中可能会出现问题。

在JS中也是一样,比如我们给ia分别赋值如下:

ia[42] = 314159;  // was 191
ia[37] = 123456;  // was 163

按照程序的书写顺序,是先给42赋值,然后给37赋值。

console.log(ia[37]);
console.log(ia[42]);

但是因为重排序的原因,可能37的值变成123456之后,42的值还是原来的191。

我们可以使用Atomics来解决这个问题,所有在Atomics.store之前的写操作,在Atomics.load发送变化之前都会发生。也就是说通过使用Atomics可以禁止重排序。

ia[42] = 314159;  // was 191
Atomics.store(ia, 37, 123456);  // was 163while (Atomics.load(ia, 37) == 163);
console.log(ia[37]);  // Will print 123456
console.log(ia[42]);  // Will print 314159

我们通过监测37的变化,如果发生了变化,则我们可以保证之前的42的修改已经发生。

同样的,我们知道在java中++操作并不是一个原子性操作,在JS中也一样。

在多线程环境中,我们需要使用Atomics的add方法来替代++操作,从而保证原子性。

注意,Atomics只适用于Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array or Uint32Array。

上面例子中,我们使用while循环来等待一个值的变化,虽然很简单,但是并不是很有效。

while循环会占用CPU资源,造成不必要的浪费。

为了解决这个问题,Atomics引入了wait和wake操作。

我们看一个应用:

console.log(ia[37]);  // Prints 163
Atomics.store(ia, 37, 123456);
Atomics.wake(ia, 37, 1);

我们希望37的值变化之后通知监听在37上的一个数组。

Atomics.wait(ia, 37, 163);
console.log(ia[37]);  // Prints 123456

当ia37的值是163的时候,线程等待在ia37上。直到被唤醒。

这就是一个典型的wait和notify的操作。

使用Atomics来创建lock

我们来使用SharedArrayBuffer和Atomics创建lock。

我们需要使用的是Atomics的CAS操作:

    compareExchange(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, expectedValue: number, replacementValue: number): number;

只有当typedArray[index]的值 = expectedValue 的时候,才会使用replacementValue来替换。 同时返回typedArray[index]的原值。

我们看下lock怎么实现:

const UNLOCKED = 0;
const LOCKED_NO_WAITERS = 1;
const LOCKED_POSSIBLE_WAITERS = 2;lock() {const iab = this.iab;const stateIdx = this.ibase;var c;if ((c = Atomics.compareExchange(iab, stateIdx,UNLOCKED, LOCKED_NO_WAITERS)) !== UNLOCKED) {do {if (c === LOCKED_POSSIBLE_WAITERS|| Atomics.compareExchange(iab, stateIdx,LOCKED_NO_WAITERS, LOCKED_POSSIBLE_WAITERS) !== UNLOCKED) {Atomics.wait(iab, stateIdx,LOCKED_POSSIBLE_WAITERS, Number.POSITIVE_INFINITY);}} while ((c = Atomics.compareExchange(iab, stateIdx,UNLOCKED, LOCKED_POSSIBLE_WAITERS)) !== UNLOCKED);}}

UNLOCKED表示目前没有上锁,LOCKED_NO_WAITERS表示已经上锁了,LOCKED_POSSIBLE_WAITERS表示上锁了,并且还有其他的worker在等待这个锁。

iab表示要上锁的SharedArrayBuffer,stateIdx是Array的index。

再看下tryLock和unlock:

tryLock() {const iab = this.iab;const stateIdx = this.ibase;return Atomics.compareExchange(iab, stateIdx, UNLOCKED, LOCKED_NO_WAITERS) === UNLOCKED;}unlock() {const iab = this.iab;const stateIdx = this.ibase;var v0 = Atomics.sub(iab, stateIdx, 1);// Wake up a waiter if there are anyif (v0 !== LOCKED_NO_WAITERS) {Atomics.store(iab, stateIdx, UNLOCKED);Atomics.wake(iab, stateIdx, 1);}}

使用CAS我们实现了JS版本的lock。

当然,有了CAS,我们可以实现更加复杂的锁操作,感兴趣的朋友,可以自行探索。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/es8-shared-memory/

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

深入理解ES8的新特性SharedArrayBuffer相关推荐

  1. es7,es8,es9新特性

    es7,es8,es9新特性 1. ES7新特性(ECMAScript 2016) ES7在ES6的基础上主要添加了两项内容: Array.prototype.includes()方法 求幂运算符(* ...

  2. ES7和ES8常见新特性

    文章目录 1.ES7新特性 1.1 Array Includes 1.2 指数运算符 2.ES8新特性 2.1 Object values 2.2 Object entries 2.3 String/ ...

  3. 深入理解Android L新特性之 页面内容amp;共享元素过渡动画

    今天我们来聊聊Android L(5.0)引入的新特性:页面内容过渡动画和页面共享动画,这两个特性都是基于我们前面已经说过的Transition动画,如果你对Transition动画不太属性,请先看我 ...

  4. Streams:深入理解Redis5.0新特性

    概述 相较于Redis4.0,Redis5.0增加了很多新的特性,而streams是其中最重要的特性之一.streams是redis 的一种基本数据结构,它是一个新的强大的支持多播的可持久化的消息队列 ...

  5. C++:深入理解C++11新特性:Chapter3:左值和右值

    Chapter3:左值和右值 1. 将右值绑定到 左值 2. 将右值绑定到 常量左值引用 3. 将右值绑定到右值引用 总结: 5. 左值,右值和右值引用 6. 引用类型可以引用的的值类型 7. 全能类 ...

  6. [转]ES7、ES8、ES9、ES10新特性大盘点

    ES7.ES8.ES9.ES10新特性大盘点 本文转自:https://mp.weixin.qq.com/s/8bov6788ivV0sHzmwrn5lw 以下文章来源于前端工匠 ,作者浪里行舟君 前 ...

  7. ECMAScript 2017(ES8)新特性简介

    文章目录 简介 Async函数 共享内存和原子操作 Object的新方法 String的新方法 逗号可以添加到函数的参数列表后面了 简介 ES8是ECMA协会在2017年6月发行的一个版本,因为是EC ...

  8. ES6、ES7、ES8、ES9、ES10、ES11、ES12、ES13新特性大全

    本文是对 ES6 至 ES13 常用到的特性的总结,关于每个特性的详细内容,都有单独的文章进行详细讲述,可以跳转到具体文章进行学习查看.学习永无止境大家一起努力 . 文章为从新到旧的顺序. ECMAS ...

  9. ES7、ES8、ES9、ES10新特性大盘点

    前言 从 ECMAScript 2016(ES7)开始,版本发布变得更加频繁,每年发布一个新版本,好在每次版本的更新内容并不多,本文会细说这些新特性,尽可能和旧知识相关联,帮你迅速上手这些特性. ES ...

最新文章

  1. CVPR 2021 | 中科大联合快手,提出人脸伪造检测新方法
  2. SpringBoot Test及注解详解(含Mockito)
  3. Grafana分析Nginx日志
  4. ubuntu下安装minicom
  5. 60秒一口Python:147个demo,助你从新手小白步步进阶编程高手,赶紧收藏!!
  6. Postgresql添加/删除触发器示例
  7. 拓端tecdat|R语言模拟和预测ARIMA模型、随机游走模型RW时间序列趋势可视化
  8. Android基础入门教程——7.3.3 Android 文件下载(2)
  9. 《凤凰项目——一个IT运维传奇故事》关于运维员的那些事
  10. vs2015如何建立c语言程序,C语言快速入门——使用Visual Studio 2015创建控制台应用程序...
  11. vue中实现简单切换图片效果
  12. 基于matlab山脊线,基于Matlab的标记分水岭分割算法
  13. 东莞厚街工业机器人展会_展会效果大起底2020东莞厚街机械展暨2020东莞国际工业自动化及机器人展览会...
  14. python四大数据类型——python筑基系列
  15. 文章阅读总结:GPT
  16. CNN数据集——自己建立数据集要点
  17. OpenGL入门学习[三]
  18. JMETER分享UBT
  19. 《考取HCIA证书,看我就够了》第一篇:华为职业认证体系及HCIA介绍
  20. PCL:点云赋色 | 自定义点云中任意一点的颜色

热门文章

  1. 众人逃离北上广后又逃回:观念不合拍还要拼爹
  2. SAP 物料评估类未填写
  3. ubuntu如何查看当前的ROS发行版本
  4. 用Meta标签代码让360双核浏览器默认极速模式
  5. SpringBoot整合MybatisPlus实战动态SQL,java分布式架构
  6. 用墨刀设计原型,易被忽略的8种玩法。
  7. Android上多进程中使用webview的问题
  8. 动画骨骼【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)...
  9. 微信调用扫一扫功能实现
  10. 2017_SIGIR_Item Silk Road: Recommending Items from Information Domains to Social Users