关于ECMAScript2017特性SharedArrayBuffer和atomics的背景知识:
- 内存管理的速成课程
- ArrayBuffers和SharedArrayBuffers的介绍
- 使用Atomics避免SharedArrayBuffers中的竞争条件
ArrayBuffers和SharedArrayBuffers的介绍
在上一部分中,我解释了像JavaScript这样的内存管理语言如何处理内存。我还解释了手动内存管理如何在C语言中工作。
当我们谈论ArrayBuffers和SharedArrayBuffers时,为什么这很重要?
这是因为ArrayBuffers为您提供了一种手动处理某些数据的方法,即使您使用的是具有自动内存管理功能的JavaScript。
为什么这是你想做的事情?
正如我们在上一部分中所讨论的那样,在自动内存管理方面存在一种权衡。对开发人员来说更容易,但它增加了一些开销。在某些情况下,这种开销会导致性能问题。
例如,当您在JS中创建变量时,引擎必须猜测这是什么类型的变量以及它应该如何在内存中表示。因为它在猜测,JS引擎通常会保留比变量真正需要的空间更多的空间。根据变量,内存插槽可能比它需要的大2-8倍,这可能导致大量浪费的内存。
此外,某些创建和使用JS对象的模式可能会使收集垃圾变得更加困难。如果您正在进行手动内存管理,则可以选择适合您正在使用的用例的分配和取消分配策略。
大多数时候,这不值得麻烦。大多数用例对性能不敏感,您需要担心手动内存管理。对于常见用例,手动内存管理可能会更慢。
但是,当您需要在低级别工作以尽可能快地使代码时,ArrayBuffers和SharedArrayBuffers会为您提供一个选项。
那么ArrayBuffer是如何工作的呢?
它基本上就像使用任何其他JavaScript数组一样。除了使用ArrayBuffer时,您不能将任何JavaScript类型放入其中,如对象或字符串。您可以添加的唯一内容是字节(您可以使用数字表示)。
我应该在这里说清楚的一点是,你实际上并没有将这个字节直接添加到ArrayBuffer中。就其本身而言,这个ArrayBuffer不知道该字节应该有多大,或者不应该将不同类型的数字转换为字节。
ArrayBuffer本身只是一堆零和一行。ArrayBuffer不知道该数组中第一个元素和第二个元素之间的除法位置。
为了提供上下文,实际上将其分解为框,我们需要将其包装在所谓的视图中。可以使用类型化数组添加这些数据视图,并且可以使用许多不同类型的类型化数组。
例如,您可以使用Int8类型的数组,将其分解为8位字节。
或者你可以有一个无符号的Int16数组,它可以将它分解为16位的数据,并且还可以像处理无符号整数一样处理它。
您甚至可以在同一个基本缓冲区上拥有多个视图。不同的视图将为您提供相同操作的不同结果。
例如,如果我们从这个ArrayBuffer的Int8视图中获取元素0和1,它将在Uint16视图中给出与元素0不同的值,即使它们包含完全相同的位。
通过这种方式,ArrayBuffer基本上就像原始内存一样。它模仿了像C这样的语言的直接内存访问。
您可能想知道为什么我们不让程序员直接访问内存而不是添加这个抽象层。直接访问内存会打开一些安全漏洞。我将在以后的部分中详细解释这个问题。
那么,什么是SharedArrayBuffer?
为了解释SharedArrayBuffers,我需要解释一下并行运行代码和JavaScript。
您可以并行运行代码以使代码运行得更快,或使代码更快地响应用户事件。为此,您需要拆分工作。
在一个典型的应用程序中,工作由一个人 - 主线程完成。我之前已经谈过这个……主线程就像一个全栈开发者。它负责JavaScript,DOM和布局。
您可以采取任何措施从主线程的工作负载中删除工作。在某些情况下,ArrayBuffers可以减少主线程必须完成的工作量。
但有时候减少主线程的工作量是不够的。有时你需要引进增援……你需要分开工作。
在大多数编程语言中,通常使用称为线程的方法来分解工作。这基本上就像有多个人在一个项目上工作。如果您的任务彼此非常独立,则可以将它们分配给不同的线程。然后,这两个线程可以同时处理它们各自的任务。
在JavaScript中,您执行此操作的方式是使用称为Web worker的东西。这些Web工作者与您在其他语言中使用的线程略有不同。默认情况下,它们不共享内存。
这意味着如果要与其他线程共享某些数据,则必须将其复制。这是通过函数postMessage完成的。
postMessage接受您放入其中的任何对象,对其进行序列化,将其发送给其他Web工作者,然后将其反序列化并放入内存中。
这是一个非常缓慢的过程。
对于某些类型的数据,如ArrayBuffers,您可以执行所谓的传输内存。这意味着移动特定的内存块,以便其他Web工作者可以访问它。
但是第一个Web工作者再也无法访问它了。
这适用于某些用例,但对于许多需要具有此类高性能并行性的用例,您真正需要的是拥有共享内存。
这就是SharedArrayBuffers为您提供的。
使用SharedArrayBuffer,两个Web工作者(两个线程)都可以写入数据并从同一块内存中读取数据。
这意味着他们没有使用postMessage的通信开销和延迟。两个Web工作人员都可以立即访问数据。
但是,同时从两个线程立即访问存在一些危险。它可以导致所谓的竞争条件。
我将在下一部分中详细解释这些内容。
SharedArrayBuffers的当前状态是什么?
SharedArrayBuffers将很快出现在所有主流浏览器中。
它们已经在Safari中运行(在Safari 10.1中)。Firefox和Chrome都将在7月/ 8月的版本中发布它们。Edge计划在秋季Windows更新中发布它们。
即使它们在所有主流浏览器中都可用,我们也不希望应用程序开发人员直接使用它们。事实上,我们建议不要这样做。您应该使用可用的最高级别的抽象。
我们所期望的是JavaScript库开发人员将创建库,使您可以更轻松,更安全地使用SharedArrayBuffers。
此外,一旦SharedArrayBuffers内置到平台中,WebAssembly就可以使用它们来实现对线程的支持。一旦到位,您就可以使用像Rust这样的语言的并发抽象,它将无畏并发作为其主要目标之一。
在接下来的部分中,我们将看看工具(原子能),这些库作者会使用来建立这些抽象,同时避免竞争条件。