页面模式偏好

29 内存模型

内存一致性模型,或称内存模型,规定了通过访问由 SharedArrayBuffer 支持的 TypedArray 实例以及通过 Atomics 对象上的方法而产生的 Shared Data Block 事件的可能顺序。当程序没有数据竞争(定义见下文)时,事件顺序表现为顺序一致,即各个代理的操作相互交错。当程序存在数据竞争时,共享内存操作可能表现为顺序不一致。例如,程序可能表现出违反因果关系的行为以及其他令人意外的现象。这些现象源自编译器转换和 CPU 的设计(例如乱序执行和推测执行)。内存模型既定义程序表现出顺序一致行为的精确条件,也定义从数据竞争中可能读取到的值。也就是说,不存在未定义行为。

内存模型被定义为对求值期间由 SharedArrayBuffer 上的抽象操作或 Atomics 对象上的方法引入的 Memory 事件施加的关系约束。

注

本节针对由 SharedArrayBuffer 上的抽象操作引入的 Memory 事件提供公理化模型。需要强调的是,与本规范其余部分不同,该模型无法用算法形式表达。抽象操作以非确定性方式引入事件,是 ECMAScript 求值的操作语义与内存模型公理语义之间的接口。这些事件的语义通过考虑一次求值中所有事件组成的图来定义。它们既不是静态语义,也不是运行时语义。这里没有已证明的算法实现,而是一组用于确定某个特定事件图是允许还是不允许的约束。

29.1 内存模型基础

共享内存访问(读取和写入)分为以下定义的两组:原子访问和数据访问。原子访问是顺序一致的,也就是说,代理集群中的所有代理都认同事件存在一个严格全序。非原子访问不存在所有代理都认同的严格全序,也就是说,它们是无序的。

注 1

不支持弱于顺序一致但强于无序的排序方式,例如 release-acquire。

Shared Data Block 事件是 ReadSharedMemory、WriteSharedMemory 或 ReadModifyWriteSharedMemory Record 中的任一种。读取事件是 ReadSharedMemory 或 ReadModifyWriteSharedMemory。写入事件是 WriteSharedMemory 或 ReadModifyWriteSharedMemory。

表 97: ReadSharedMemory 事件字段
字段名称 值 含义
[[Order]] seq-cst or unordered 内存模型为该事件保证的最弱排序。
[[NoTear]] a Boolean 是否允许此事件从与此事件具有相同内存范围的多个写入事件中读取。
[[Block]] a Shared Data Block 事件所操作的数据块。
[[ByteIndex]] a non-negative integer [[Block]] 中读取操作的字节地址。
[[ElementSize]] a non-negative integer 读取操作的大小。
表 98: WriteSharedMemory 事件字段
字段名称 值 含义
[[Order]] seq-cst, unordered, or init 内存模型为该事件保证的最弱排序。
[[NoTear]] a Boolean 是否允许多个与此事件具有相同内存范围的读取事件从该事件中读取。
[[Block]] a Shared Data Block 事件所操作的数据块。
[[ByteIndex]] a non-negative integer [[Block]] 中写入操作的字节地址。
[[ElementSize]] a non-negative integer 写入操作的大小。
[[Payload]] a List of byte values 供其他事件读取的字节值 List。
表 99: ReadModifyWriteSharedMemory 事件字段
字段名称 值 含义
[[Order]] seq-cst 读-修改-写事件始终是顺序一致的。
[[NoTear]] true 读-修改-写事件不能发生撕裂。
[[Block]] a Shared Data Block 事件所操作的数据块。
[[ByteIndex]] a non-negative integer [[Block]] 中读-修改-写操作的字节地址。
[[ElementSize]] a non-negative integer 读-修改-写操作的大小。
[[Payload]] a List of byte values 要传递给 [[ModifyOp]] 的字节值 List。
[[ModifyOp]] a read-modify-write modification function 从读取到的字节值 List 和 [[Payload]] 返回修改后字节值 List 的 Abstract Closure。

Shared Data Block 事件由抽象操作或 Atomics 对象上的方法引入候选执行的 Agent Events Record 中。某些操作还会引入Synchronize 事件,这些事件没有字段,仅用于直接约束其他事件允许的排序。最后,还存在宿主特定事件。Memory 事件是 Shared Data Block 事件、Synchronize 事件或此类宿主特定事件中的任一种。

令 Shared Data Block 事件 e 的内存范围为从 e.[[ByteIndex]](包含)到 e.[[ByteIndex]] + e.[[ElementSize]](不包含)的区间中所有整数构成的 Set。如果两个事件具有相同的 [[Block]]、[[ByteIndex]] 和 [[ElementSize]],则称两个事件的内存范围相等。如果两个事件具有相同的 [[Block]],范围不相等,并且它们的交集非空,则称两个事件的内存范围重叠。如果两个事件不具有相同的 [[Block]],或者它们的范围既不相等也不重叠,则称两个事件的内存范围不相交。

注 2

应当考虑的宿主特定同步事件示例包括:将 SharedArrayBuffer 从一个代理发送到另一个代理(例如浏览器中的 postMessage)、启动和停止代理,以及通过共享内存以外的通道在代理集群内通信。对于特定执行 execution,这些事件由宿主通过 host-synchronizes-with 严格偏序提供。此外,宿主还可以向 execution.[[EventList]] 添加宿主特定的同步事件,使其参与 is-agent-order-before Relation。

候选执行中的事件按照下文定义的关系排序。

29.2 Agent Events Record

Agent Events Record 是具有以下字段的 Record。

表 100: Agent Events Record 字段
字段名称 值 含义
[[AgentSignifier]] an agent signifier 其求值产生此排序的代理。
[[EventList]] a List of Memory events 事件在求值期间追加到该 List。
[[AgentSynchronizesWith]] a List of pairs of Synchronize events 由操作语义引入的 Synchronize 关系。

29.3 Chosen Value Record

Chosen Value Record 是具有以下字段的 Record。

表 101: Chosen Value Record 字段
字段名称 值 含义
[[Event]] a Shared Data Block event 为此选定值引入的 ReadSharedMemory 或 ReadModifyWriteSharedMemory 事件。
[[ChosenValue]] a List of byte values 求值期间非确定性选择的字节。

29.4 候选执行

代理集群求值的候选执行是具有以下字段的 Record。

表 102: 候选执行 Record 字段
字段名称 值 含义
[[EventsRecords]] a List of Agent Events Records 将代理映射到求值期间追加的 Memory 事件 List。
[[ChosenValues]] a List of Chosen Value Records 将 ReadSharedMemory 或 ReadModifyWriteSharedMemory 事件映射到求值期间选择的字节值 List。

空候选执行是字段均为空 List 的候选执行 Record。

29.5 内存模型的抽象操作

29.5.1 EventSet ( execution )

抽象操作 EventSet,接收参数 execution (a candidate execution);返回值:a Set of Memory events。 调用时执行以下步骤:

  1. 令 events 为空 Set。
  2. 对 execution.[[EventsRecords]] 的每个 Agent Events Record eventsRecord,执行
    1. 对 eventsRecord.[[EventList]] 的每个 Memory event event,执行
      1. 将 event 添加到 events。
  3. 返回 events。

29.5.2 SharedDataBlockEventSet ( execution )

抽象操作 SharedDataBlockEventSet,接收参数 execution (a candidate execution);返回值:a Set of Shared Data Block events。 调用时执行以下步骤:

  1. 令 events 为空 Set。
  2. 对 EventSet(execution) 的每个 Memory event event,执行
    1. 如果 event 是 Shared Data Block event,则将 event 添加到 events。
  3. 返回 events。

29.5.3 HostEventSet ( execution )

抽象操作 HostEventSet,接收参数 execution (a candidate execution);返回值:a Set of Memory events。 调用时执行以下步骤:

  1. 返回一个新的 Set,其中包含 EventSet(execution) 中所有不属于 SharedDataBlockEventSet(execution) 的元素。

29.5.4 ComposeWriteEventBytes ( execution, byteIndex, writes )

抽象操作 ComposeWriteEventBytes,接收参数 execution (a candidate execution)、byteIndex (非负整数) 和 writes (列表 (元素:(WriteSharedMemory 或 ReadModifyWriteSharedMemory events)));返回值:列表 (元素:字节值)。 调用时执行以下步骤:

  1. 令 byteLocation 为 byteIndex。
  2. 令 bytesRead 为新的空 List。
  3. 对 writes 的每个元素 writeEvent,执行
    1. 断言:writeEvent 的内存范围包含 byteLocation。
    2. 令 payloadIndex 为 byteLocation - writeEvent.[[ByteIndex]]。
    3. 如果 writeEvent 是 WriteSharedMemory event,则
      1. 令 byte 为 writeEvent.[[Payload]][payloadIndex]。
    4. 否则,
      1. 断言:writeEvent 是 ReadModifyWriteSharedMemory event。
      2. 令 bytes 为 ValueOfReadEvent(execution, writeEvent)。
      3. 令 bytesModified 为 writeEvent.[[ModifyOp]](bytes, writeEvent.[[Payload]])。
      4. 令 byte 为 bytesModified[payloadIndex]。
    5. 将 byte 追加到 bytesRead。
    6. 将 byteLocation 设置为 byteLocation + 1。
  4. 返回 bytesRead。
注 1

读-修改-写修改 [[ModifyOp]] 由 Atomics 对象上会引入 ReadModifyWriteSharedMemory 事件的函数属性给出。

注 2

此抽象操作将写入事件 List 组合成字节值 List。它用于 ReadSharedMemory 和 ReadModifyWriteSharedMemory 事件的事件语义。

29.5.5 ValueOfReadEvent ( execution, readEvent )

抽象操作 ValueOfReadEvent,接收参数 execution (a candidate execution) 和 readEvent ((a ReadSharedMemory 或 ReadModifyWriteSharedMemory event));返回值:列表 (元素:字节值)。 调用时执行以下步骤:

  1. 令 writes 为 reads-bytes-from(readEvent) in execution。
  2. 断言:writes 是 WriteSharedMemory 或 ReadModifyWriteSharedMemory 事件的 List,其长度等于 readEvent.[[ElementSize]]。
  3. 返回 ComposeWriteEventBytes(execution, readEvent.[[ByteIndex]], writes)。

29.6 候选执行的关系

以下关系和数学函数以某个特定候选执行为参数,并对其 Memory 事件进行排序。

29.6.1 is-agent-order-before

对于候选执行 execution,其 is-agent-order-before Relation 是满足以下条件的 Memory 事件上的最小 Relation。

  • 对于事件 eventA 和 eventB,如果 execution.[[EventsRecords]] 中存在某个 Agent Events Record eventsRecord,使得 eventsRecord.[[EventList]] 同时包含 eventA 和 eventB,并且按照 eventsRecord.[[EventList]] 的 List 顺序,eventA 位于 eventB 之前,则 eventA is-agent-order-before eventB in execution。
注

每个代理在求值期间都按每代理严格全序引入事件。这是这些严格全序的并集。

29.6.2 reads-bytes-from

对于候选执行 execution,其 reads-bytes-from 函数是一个数学函数,它将 SharedDataBlockEventSet(execution) 中的 Memory 事件映射到 SharedDataBlockEventSet(execution) 中的事件 List,并满足以下条件。

候选执行始终允许存在 reads-bytes-from 函数。

29.6.3 reads-from

对于候选执行 execution,其 reads-from Relation 是满足以下条件的 Memory 事件上的最小 Relation。

29.6.4 host-synchronizes-with

对于候选执行 execution,其 host-synchronizes-with Relation 是由宿主提供的宿主特定 Memory 事件上的严格偏序,并且至少满足以下条件。

  • 如果 eventA host-synchronizes-with eventB in execution,则 HostEventSet(execution) 包含 eventA 和 eventB。
  • execution 中 host-synchronizes-with 和 is-agent-order-before 的并集中不存在环。
注 1

对于候选执行 execution 中两个宿主特定事件 eventA 和 eventB,eventA host-synchronizes-with eventB in execution 蕴含 eventA happens-before eventB in execution。

注 2

此 Relation 允许宿主提供其他同步机制,例如 HTML worker 之间的 postMessage。

29.6.5 synchronizes-with

对于候选执行 execution,其 synchronizes-with Relation 是满足以下条件的 Memory 事件上的最小 Relation。

  • 对于事件 readEvent 和 writeEvent,如果 readEvent reads-from writeEvent in execution、readEvent.[[Order]] 是 seq-cst、writeEvent.[[Order]] 是 seq-cst,并且 readEvent 和 writeEvent 具有相等的内存范围,则 writeEvent synchronizes-with readEvent in execution。
  • 对于 execution.[[EventsRecords]] 的每个元素 eventsRecord,以下条件成立。
    • 对于事件 eventA 和 eventB,如果 eventsRecord.[[AgentSynchronizesWith]] 包含 (eventA, eventB),则 eventA synchronizes-with eventB in execution。
  • 对于事件 eventA 和 eventB,如果 eventA host-synchronizes-with eventB in execution,则 eventA synchronizes-with eventB in execution。
注 1

按照内存模型文献中的惯例,在候选执行 execution 中,是写入事件 synchronizes-with 读取事件,而不是读取事件 synchronizes-with 写入事件。

注 2

在候选执行 execution 中,init 事件不参与此 Relation,而是由 happens-before 直接约束。

注 3

在候选执行 execution 中,并非所有通过 reads-from 相关联的 seq-cst 事件也通过 synchronizes-with 相关联。只有同时具有相等内存范围的事件才通过 synchronizes-with 相关联。

注 4

对于候选执行 execution 中满足 writeEvent synchronizes-with readEvent 的 Shared Data Block 事件 readEvent 和 writeEvent,readEvent 可能 reads-from writeEvent 之外的其他写入。

29.6.6 happens-before

对于候选执行 execution,其 happens-before Relation 是满足以下条件的 Memory 事件上的最小 Relation。

  • 对于事件 eventA 和 eventB,如果以下任一条件为真,则 eventA happens-before eventB in execution。

注

由于 happens-before 是 agent-order 的超集,因此候选执行与 ECMAScript 的单线程求值语义一致。

29.7 有效执行的属性

29.7.1 有效的选定读取

如果以下算法返回 true,则候选执行 execution 具有有效的选定读取。

  1. 对 SharedDataBlockEventSet(execution) 的每个 ReadSharedMemory 或 ReadModifyWriteSharedMemory event readEvent,执行
    1. 令 chosenValueRecord 为 execution.[[ChosenValues]] 中 [[Event]] 字段为 readEvent 的元素。
    2. 令 chosenValue 为 chosenValueRecord.[[ChosenValue]]。
    3. 令 readValue 为 ValueOfReadEvent(execution, readEvent)。
    4. 令 chosenLength 为 chosenValue 中的元素数量。
    5. 令 readLength 为 readValue 中的元素数量。
    6. 如果 chosenLength ≠ readLength,则
      1. 返回 false。
    7. 如果对于从 0(包含)到 chosenLength(不包含)区间中的某个整数 i,chosenValue[i] ≠ readValue[i],则
      1. 返回 false。
  2. 返回 true。

29.7.2 一致读取

如果以下算法返回 true,则候选执行 execution 具有一致读取。

  1. 对 SharedDataBlockEventSet(execution) 的每个 ReadSharedMemory 或 ReadModifyWriteSharedMemory event readEvent,执行
    1. 令 writes 为 reads-bytes-from(readEvent) in execution。
    2. 令 byteLocation 为 readEvent.[[ByteIndex]]。
    3. 对 writes 的每个元素 writeEvent,执行
      1. 如果 readEvent happens-before writeEvent in execution,则
        1. 返回 false。
      2. 如果存在 WriteSharedMemory 或 ReadModifyWriteSharedMemory event value,其内存范围包含 byteLocation,并且 writeEvent happens-before value in execution 且 value happens-before readEvent in execution,则
        1. 返回 false。
      3. 将 byteLocation 设置为 byteLocation + 1。
  2. 返回 true。

29.7.3 无撕裂读取

如果以下算法返回 true,则候选执行 execution 具有无撕裂读取。

  1. 对 SharedDataBlockEventSet(execution) 的每个 ReadSharedMemory 或 ReadModifyWriteSharedMemory event readEvent,执行
    1. 如果 readEvent.[[NoTear]] 是 true,则
      1. 断言:readEvent.[[ByteIndex]] 除以 readEvent.[[ElementSize]] 的余数为 0。
      2. 对满足 readEvent reads-from writeEvent in execution 且 writeEvent.[[NoTear]] 是 true 的每个 Memory event writeEvent,执行
        1. 如果 readEvent 和 writeEvent 具有相等的内存范围,并且存在 Memory event value,使得 value 和 writeEvent 具有相等的内存范围、value.[[NoTear]] 是 true、writeEvent 与 value 不是同一个 Shared Data Block event,并且 readEvent reads-from value in execution,则
          1. 返回 false。
  2. 返回 true。
注

当 Shared Data Block event 是通过访问整数 TypedArray 引入时,其 [[NoTear]] 字段为 true;当通过访问浮点 TypedArray 或 DataView 引入时,则为 false。

直观地说,此要求意味着,当通过整数 TypedArray 以对齐方式访问某个内存范围时,如果对该范围的一次写入事件与其他具有相同范围的写入事件发生数据竞争,则必须有一个写入事件“胜出”。更准确地说,此要求意味着,对齐读取事件不能读取由多个不同写入事件的字节组合而成的值,而这些写入事件都具有相同范围。不过,对齐读取事件仍可能从多个具有重叠范围的写入事件中读取。

29.7.4 顺序一致的原子操作

对于候选执行 execution,is-memory-order-before 是 EventSet(execution) 中所有 Memory 事件的严格全序,并满足以下条件。

如果候选执行允许存在 is-memory-order-before Relation,则它具有顺序一致的原子操作。

注 3

虽然 is-memory-order-before 包含 EventSet(execution) 中的所有事件,但在 execution 中不受 happens-before 或 synchronizes-with 约束的事件可以出现在该顺序中的任何位置。

29.7.5 有效执行

如果以下所有条件均为真,则候选执行 execution 是有效执行(或简称执行)。

所有程序至少具有一个有效执行。

29.8 竞争

对于执行 execution 以及包含在 SharedDataBlockEventSet(execution) 中的事件 eventA 和 eventB,如果以下算法返回 true,则 eventA 和 eventB 处于竞争中。

  1. 如果 eventA 和 eventB 不是同一个 Shared Data Block event,则
    1. 如果并非 eventA happens-before eventB in execution 和 eventB happens-before eventA in execution 同时成立,则
      1. 如果 eventA 是 WriteSharedMemory 或 ReadModifyWriteSharedMemory event、eventB 是 WriteSharedMemory 或 ReadModifyWriteSharedMemory event,并且 eventA 和 eventB 的内存范围并非不相交,则
        1. 返回 true。
      2. 如果 eventA reads-from eventB in execution 或 eventB reads-from eventA in execution,则
        1. 返回 true。
  2. 返回 false。

29.9 数据竞争

对于执行 execution 以及包含在 SharedDataBlockEventSet(execution) 中的事件 eventA 和 eventB,如果以下算法返回 true,则 eventA 和 eventB 处于数据竞争中。

  1. 如果 eventA 和 eventB 在 execution 中处于竞争中,则
    1. 如果 eventA.[[Order]] 不是 seq-cst 或 eventB.[[Order]] 不是 seq-cst,则
      1. 返回 true。
    2. 如果 eventA 和 eventB 具有重叠的内存范围,则
      1. 返回 true。
  2. 返回 false。

29.10 无数据竞争

如果 SharedDataBlockEventSet(execution) 中不存在两个处于数据竞争中的事件,则执行 execution 是无数据竞争的。

如果程序的所有执行都是无数据竞争的,则该程序是无数据竞争的。

内存模型保证无数据竞争程序中所有事件的顺序一致性。

29.11 共享内存指南

注 1

以下是供使用共享内存的 ECMAScript 程序员参考的指南。

建议使程序保持无数据竞争,也就是说,使同一内存位置上不可能出现并发非原子操作。无数据竞争程序具有交错语义,每个代理求值语义中的每个步骤彼此交错。对于无数据竞争程序,无需理解内存模型的细节。这些细节不太可能帮助建立更好编写 ECMAScript 代码所需的直觉。

更一般地说,即使程序并非无数据竞争,只要原子操作不参与任何数据竞争,并且发生竞争的操作都具有相同的访问大小,其行为仍可能是可预测的。确保原子操作不参与竞争的最简单方式,是让原子操作和非原子操作使用不同的内存单元,并且不要同时使用不同大小的原子访问来访问相同单元。实际上,程序应尽可能将共享内存视为强类型内存。仍然不能依赖发生竞争的非原子访问的顺序和时机,但如果将内存视为强类型,则发生竞争的访问不会“撕裂”(其值的位不会混合)。

注 2

以下是供为使用共享内存的程序编写编译器转换的 ECMAScript 实现者参考的指南。

为了确保多代理程序中每个代理的性能与单代理环境中一样好,希望允许大多数在单代理环境中有效的程序转换在多代理环境中同样有效。这些转换往往很难判断。下面概述一些关于程序转换的规则,这些规则旨在被视为规范性规则(因为它们由内存模型隐含,或强于内存模型所隐含的内容),但很可能并不穷尽。这些规则旨在适用于引入构成 is-agent-order-before Relation 的 Memory 事件之前进行的程序转换。

令agent-order 切片为 is-agent-order-before Relation 中属于单个代理的子集。

令读取事件的可能读取值为该事件在所有有效执行中的所有 ValueOfReadEvent 值的集合。

在没有共享内存时有效的任何 agent-order 切片转换,在存在共享内存时也有效,但以下情况除外。

  • 原子操作不可撼动:程序转换不得导致 [[Order]] 为 seq-cst 的任何 Shared Data Block 事件从 is-agent-order-before Relation 中移除,不得相互重排,也不得在 agent-order 切片内相对于 [[Order]] 为 unordered 的事件重新排序。

    (实际上,对重排的禁止迫使编译器假定每个 seq-cst 操作都是同步操作并包含在最终的 is-memory-order-before Relation 中;在缺少代理间程序分析的情况下,它通常无论如何都必须作出这种假设。它还迫使编译器假定,每个其被调用者对 memory-order 的影响未知的调用都可能包含 seq-cst 操作。)

  • 读取必须稳定:任何给定的共享内存读取在一次执行中都只能观察到一个值。

    (例如,如果程序中语义上是单次读取的操作被执行多次,那么程序随后只能观察读取到的其中一个值。称为重物化的转换可能违反此规则。)

  • 写入必须稳定:对共享内存的所有可观察写入都必须源自一次执行中的程序语义。

    (例如,转换不得引入某些可观察写入,例如通过对更大位置使用读-修改-写操作来写入较小数据、向内存写入程序不可能写入的值,或者在某位置可能在读取后已被另一代理覆盖的情况下,将刚刚读取的值写回其读取位置。)

  • 可能读取值必须非空:程序转换不能导致共享内存读取的可能读取值变为空。

    (反直觉的是,此规则实际上限制了对写入的转换,因为写入在内存模型中的作用在于可被读取事件读取。例如,可以移动和合并写入,有时还可以在两个 seq-cst 操作之间重排写入,但转换不得移除更新某个位置的所有写入;必须保留某个写入。)

仍然有效的转换示例包括:合并对同一位置的多个非原子读取、重排非原子读取、引入推测性非原子读取、合并对同一位置的多个非原子写入、重排对不同位置的非原子写入,以及即使会影响终止性也将非原子读取提升到循环之外。一般而言,需要注意别名 TypedArray 会使证明位置彼此不同变得困难。

注 3

以下是供为共享内存访问生成机器代码的 ECMAScript 实现者参考的指南。

对于内存模型不弱于 ARM 或 Power 的架构,非原子存储和加载可以编译为目标架构上的裸存储和裸加载。原子存储和加载可以编译为保证顺序一致性的指令。如果不存在此类指令,则应使用内存屏障,例如在裸存储或加载的两侧放置屏障。读-修改-写操作可以编译为目标架构上的读-修改-写指令,例如 x86 上带 LOCK 前缀的指令、ARM 上的 load-exclusive/store-exclusive 指令,以及 Power 上的 load-link/store-conditional 指令。

具体来说,内存模型旨在允许按以下方式生成代码。

  • 假定程序中的每个原子操作都是必需的。
  • 原子操作永远不会彼此重排,也不会与非原子操作重排。
  • 始终假定函数会执行原子操作。
  • 原子操作永远不会实现为在更大数据上的读-修改-写操作;如果平台没有适当大小的原子操作,则实现为非无锁原子操作。(我们已经假定每个平台都具有所有相关大小的普通内存访问操作。)

朴素代码生成使用以下模式:

  • 普通加载和存储编译为单个加载和存储指令。
  • 无锁原子加载和存储编译为一个完整(顺序一致)屏障、一次普通加载或存储以及另一个完整屏障。
  • 无锁原子读-修改-写访问编译为一个完整屏障、一段原子读-修改-写指令序列以及另一个完整屏障。
  • 非无锁原子操作编译为获取自旋锁、一个完整屏障、一系列非原子加载和存储指令、一个完整屏障,以及释放自旋锁。

只要某个内存范围上的原子操作不与非原子写入或不同大小的原子操作发生竞争,这种映射就是正确的。不过,这正是我们所需要的:内存模型实际上将参与竞争的原子操作降级为非原子状态。另一方面,朴素映射相当强:它允许将原子操作用作顺序一致的屏障,而内存模型实际上并不保证这一点。

在内存模型约束下,也允许对这些基本模式进行局部改进。例如:

  • 存在明显的依赖平台的优化,可以移除冗余屏障。例如,在 x86 上,无锁原子加载和存储周围的屏障始终可以省略,但存储后的屏障除外;无锁读-修改-写指令不需要屏障,因为这些指令都使用带 LOCK 前缀的指令。许多平台具有多种强度的屏障,可以在特定上下文中使用较弱的屏障而不会破坏顺序一致性。
  • 大多数现代平台对 ECMAScript 原子操作所需的所有数据大小都支持无锁原子操作。如果需要非无锁原子操作,原子操作主体周围的屏障通常可以并入加锁和解锁步骤。对于非无锁原子操作,最简单的解决方案是每个 SharedArrayBuffer 使用一个锁字。
  • 还有一些更复杂、依赖平台的局部改进,需要一定的代码分析。例如,连续的两个屏障通常与单个屏障效果相同,因此如果连续为两个原子操作生成代码,只需一个屏障将它们分开。在 x86 上,即使分隔原子存储的单个屏障也可以省略,因为存储后的屏障只在需要将该存储与后续加载分隔时才有必要。