29 内存模型

内存一致性模型,或称内存模型,指定 Shared Data Block 事件的可能排序,这些事件源自访问由 SharedArrayBuffer 支撑的 TypedArray 实例,以及源自 Atomics 对象上的方法。当程序没有数据竞争(定义如下)时,事件的排序表现为顺序一致,即表现为来自每个 agent 的动作交错。当程序有数据竞争时,共享内存操作可能表现为顺序不一致。例如,程序可能表现出违反因果性的行为及其他令人惊讶之处。这些令人惊讶之处源自编译器转换和 CPU 的设计(例如乱序执行和推测执行)。内存模型既定义了程序表现出顺序一致行为的精确条件,也定义了从数据竞争中读取到的可能值。也就是说,不存在未定义行为。

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

Note

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

29.1 内存模型基础

共享内存访问(读和写)分为两组:原子访问和数据访问,定义如下。原子访问是顺序一致的,即,在一个 agent cluster 中所有 agent 都同意的严格全序事件排序。非原子访问没有所有 agent 都同意的严格全序,即无序。

Note 1

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

Shared Data Block eventReadSharedMemoryWriteSharedMemoryReadModifyWriteSharedMemory Recordread event 是 ReadSharedMemory 或 ReadModifyWriteSharedMemory。write event 是 WriteSharedMemory 或 ReadModifyWriteSharedMemory。

Table 97: ReadSharedMemory Event 字段
字段名 含义
[[Order]] seq-cstunordered 内存模型为该事件保证的最弱排序。
[[NoTear]] Boolean 此事件是否允许从与此事件内存范围相等的多个 write event 中读取。
[[Block]] Shared Data Block 该事件操作的块。
[[ByteIndex]] 非负整数 [[Block]] 中该读的字节地址。
[[ElementSize]] 非负整数 该读的大小。
Table 98: WriteSharedMemory Event 字段
字段名 含义
[[Order]] seq-cstunorderedinit 内存模型为该事件保证的最弱排序。
[[NoTear]] Boolean 此事件是否允许被与此事件内存范围相等的多个 read event 读取。
[[Block]] Shared Data Block 该事件操作的块。
[[ByteIndex]] 非负整数 [[Block]] 中该写的字节地址。
[[ElementSize]] 非负整数 该写的大小。
[[Payload]] 字节值List 将被其他事件读取的字节值 List
Table 99: ReadModifyWriteSharedMemory Event 字段
字段名 含义
[[Order]] seq-cst 读-改-写事件总是顺序一致的。
[[NoTear]] true 读-改-写事件不能撕裂。
[[Block]] Shared Data Block 该事件操作的块。
[[ByteIndex]] 非负整数 [[Block]] 中该读-改-写的字节地址。
[[ElementSize]] 非负整数 该读-改-写的大小。
[[Payload]] 字节值List 要传给 [[ModifyOp]]字节值 List
[[ModifyOp]] 读-改-写修改函数 一个 Abstract Closure,它从读取到的字节值 List[[Payload]] 返回修改后的字节值 List

共享数据块事件由抽象操作或 Atomics 对象上的方法引入候选执行的 Agent Events 记录中。某些操作还会引入同步事件,它们没有字段,纯粹用于直接约束其他事件的允许排序。最后,还有宿主特定事件。内存事件是共享数据块事件、同步事件或这样的宿主特定事件。

令共享数据块事件 e内存范围为从 e.[[ByteIndex]]包含)到 e.[[ByteIndex]] + e.[[ElementSize]](不包含)这一间隔内所有整数的集合。当两个事件具有相同的 [[Block]][[ByteIndex]][[ElementSize]] 时,这两个事件的内存范围相等。当两个事件具有相同的 [[Block]]、范围不相等且它们的交集非空时,这两个事件的内存范围重叠。当两个事件没有相同的 [[Block]],或者它们的范围既不相等也不重叠时,这两个事件的内存范围不相交。

Note 2

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

在候选执行内,事件按下面定义的关系排序。

29.2 Agent Events Record

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

Table 100: Agent Events Record 字段
字段名 含义
[[AgentSignifier]] agent signifier 其求值产生了此排序的 agent
[[EventList]] Memory event 的 List 在求值期间,事件被追加到该 List
[[AgentSynchronizesWith]] Synchronize event 对的 List 由操作语义引入的 Synchronize 关系。

29.3 Chosen Value Record

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

Table 101: Chosen Value Record 字段
字段名 含义
[[Event]] Shared Data Block event 为此 chosen value 引入的 ReadSharedMemoryReadModifyWriteSharedMemory event。
[[ChosenValue]] 字节值List 在求值期间非确定性选择的字节。

29.4 Candidate Execution

agent cluster 的求值的 candidate execution 是具有以下字段的 Record

Table 102: Candidate Execution Record 字段
字段名 含义
[[EventsRecords]] Agent Events RecordList agent 映射到在求值期间追加的 Memory event 的 List
[[ChosenValues]] Chosen Value RecordList ReadSharedMemoryReadModifyWriteSharedMemory event 映射到求值期间选择的字节值 List

空 candidate execution 是其字段为空 List 的 candidate execution Record

29.5 内存模型的抽象操作

29.5.1 EventSet ( execution )

The abstract operation EventSet takes argument execution (a candidate execution) and returns a Set of Memory events. It performs the following steps when called:

  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 )

The abstract operation SharedDataBlockEventSet takes argument execution (a candidate execution) and returns a Set of Shared Data Block events. It performs the following steps when called:

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

29.5.3 HostEventSet ( execution )

The abstract operation HostEventSet takes argument execution (a candidate execution) and returns a Set of Memory events. It performs the following steps when called:

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

29.5.4 ComposeWriteEventBytes ( execution, byteIndex, writes )

The abstract operation ComposeWriteEventBytes takes arguments execution (a candidate execution), byteIndex (a non-negative integer), and writes (a List of either WriteSharedMemory or ReadModifyWriteSharedMemory events) and returns a List of byte values. It performs the following steps when called:

  1. byteLocationbyteIndex
  2. bytesRead 为新的空 List
  3. writes 的每个元素 writeEvent,执行:
    1. 断言:writeEvent内存范围包含 byteLocation
    2. payloadIndexbyteLocation - writeEvent.[[ByteIndex]]
    3. 如果 writeEventWriteSharedMemory event,则
      1. bytewriteEvent.[[Payload]][payloadIndex]。
    4. 否则,
      1. 断言:writeEventReadModifyWriteSharedMemory event。
      2. bytesValueOfReadEvent(execution, writeEvent)。
      3. bytesModifiedwriteEvent.[[ModifyOp]](bytes, writeEvent.[[Payload]])。
      4. bytebytesModified[payloadIndex]。
    5. byte 追加到 bytesRead
    6. byteLocation 设置为 byteLocation + 1。
  4. 返回 bytesRead
Note 1

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

Note 2

抽象操作write eventList 组合成字节值List。它用于 ReadSharedMemoryReadModifyWriteSharedMemory event 的事件语义。

29.5.5 ValueOfReadEvent ( execution, readEvent )

The abstract operation ValueOfReadEvent takes arguments execution (a candidate execution) and readEvent (a ReadSharedMemory or ReadModifyWriteSharedMemory event) and returns a List of byte values. It performs the following steps when called:

  1. writesexecution 中的 reads-bytes-from(readEvent)。
  2. 断言:writes 是长度等于 readEvent.[[ElementSize]]WriteSharedMemoryReadModifyWriteSharedMemory event 的 List
  3. 返回 ComposeWriteEventBytes(execution, readEvent.[[ByteIndex]], writes)。

29.6 Candidate Execution 的关系

以下关系和数学函数以特定 candidate execution 为参数,并对其 Memory event 排序。

29.6.1 is-agent-order-before

对于 candidate execution execution,其 is-agent-order-before 关系是 Memory event 上满足以下条件的最小关系。

  • 对于事件 eventAeventB,如果在 execution.[[EventsRecords]] 中存在某个 Agent Events Record eventsRecord,使得 eventsRecord.[[EventList]] 同时包含 eventAeventB,并且 eventAeventsRecord.[[EventList]]List 顺序中位于 eventB 之前,则 eventAexecution 中 is-agent-order-before eventB
Note

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

29.6.2 reads-bytes-from

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

candidate execution 总是允许存在 reads-bytes-from 函数。

29.6.3 reads-from

对于 candidate execution execution,其 reads-from 关系是 Memory event 上满足以下条件的最小关系。

29.6.4 host-synchronizes-with

对于 candidate execution execution,其 host-synchronizes-with 关系是宿主提供的宿主特定 Memory event 上的严格偏序,并且至少满足以下条件。

  • 如果 eventAexecution 中 host-synchronizes-with eventB,则 HostEventSet(execution) 包含 eventAeventB
  • execution 中,host-synchronizes-with 与 is-agent-order-before 的并集中没有环。
Note 1

对于 candidate execution execution 中的两个宿主特定事件 eventAeventBeventAexecution 中 host-synchronizes-with eventB 蕴含 eventAexecutionhappens-before eventB

Note 2

此关系允许宿主提供额外的同步机制,例如 HTML worker 之间的 postMessage

29.6.5 synchronizes-with

对于 candidate execution execution,其 synchronizes-with 关系是 Memory event 上满足以下条件的最小关系。

  • 对于事件 readEventwriteEvent,如果 readEventexecutionreads-from writeEventreadEvent.[[Order]]seq-cstwriteEvent.[[Order]]seq-cst,且 readEventwriteEvent 具有相等的内存范围,则 writeEventexecution 中 synchronizes-with readEvent
  • execution.[[EventsRecords]] 的每个元素 eventsRecord,以下为真。
    • 对于事件 eventAeventB,如果 eventsRecord.[[AgentSynchronizesWith]] 包含 (eventA, eventB),则 eventAexecution 中 synchronizes-with eventB
  • 对于事件 eventAeventB,如果 eventAexecutionhost-synchronizes-with eventB,则 eventAexecution 中 synchronizes-with eventB
Note 1

由于内存模型文献中的约定,在 candidate execution execution 中,是 write event synchronizes-with read event,而不是 read event synchronizes-with write event

Note 2

candidate execution execution 中,init 事件不参与此关系,而是由 happens-before 直接约束。

Note 3

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

Note 4

对于 candidate execution execution 中的 Shared Data Block event readEventwriteEvent,如果 writeEvent synchronizes-with readEvent,则 readEvent 可以 reads-from writeEvent 以外的其他写。

29.6.6 happens-before

对于 candidate execution execution,其 happens-before 关系是 Memory event 上满足以下条件的最小关系。

  • 对于事件 eventAeventB,如果以下任一条件为真,则 eventAexecution 中 happens-before eventB

    • eventAexecutionis-agent-order-before eventB
    • eventAexecutionsynchronizes-with eventB
    • SharedDataBlockEventSet(execution) 同时包含 eventAeventBeventA.[[Order]]init,且 eventAeventB 具有重叠的内存范围
    • 存在事件 eventC,使得 eventAexecution 中 happens-before eventC,并且 eventCexecution 中 happens-before eventB
Note

因为 happens-before 是 agent-order 的超集,所以 candidate execution 与 ECMAScript 的单线程求值语义一致。

29.7 有效执行的属性

29.7.1 有效的 Chosen Read

如果以下算法返回 true,则 candidate execution execution 具有有效的 chosen read。

  1. SharedDataBlockEventSet(execution) 的每个 ReadSharedMemoryReadModifyWriteSharedMemory event readEvent,执行:
    1. chosenValueRecordexecution.[[ChosenValues]] 中其 [[Event]] 字段为 readEvent 的元素。
    2. chosenValuechosenValueRecord.[[ChosenValue]]
    3. readValueValueOfReadEvent(execution, readEvent)。
    4. chosenLengthchosenValue 中元素的数量。
    5. readLengthreadValue 中元素的数量。
    6. 如果 chosenLengthreadLength,则
      1. 返回 false
    7. 如果对于从 0(含)到 chosenLength(不含)区间内的某个整数 ichosenValue[i] ≠ readValue[i],则
      1. 返回 false
  2. 返回 true

29.7.2 相干读

如果以下算法返回 true,则 candidate execution execution 具有相干读。

  1. SharedDataBlockEventSet(execution) 的每个 ReadSharedMemoryReadModifyWriteSharedMemory event readEvent,执行:
    1. writesexecution 中的 reads-bytes-from(readEvent)。
    2. byteLocationreadEvent.[[ByteIndex]]
    3. writes 的每个元素 writeEvent,执行:
      1. 如果 readEventexecutionhappens-before writeEvent,则
        1. 返回 false
      2. 如果存在一个 WriteSharedMemoryReadModifyWriteSharedMemory event value,其内存范围包含 byteLocation,使得 writeEventexecutionhappens-before value,且 valueexecutionhappens-before readEvent,则
        1. 返回 false
      3. byteLocation 设置为 byteLocation + 1。
  2. 返回 true

29.7.3 无撕裂读

如果以下算法返回 true,则 candidate execution execution 具有无撕裂读。

  1. SharedDataBlockEventSet(execution) 的每个 ReadSharedMemoryReadModifyWriteSharedMemory event readEvent,执行:
    1. 如果 readEvent.[[NoTear]]true,则
      1. 断言:readEvent.[[ByteIndex]] 除以 readEvent.[[ElementSize]] 的余数为 0。
      2. 对满足如下条件的每个 Memory event writeEvent 执行:readEventexecutionreads-from writeEvent,并且 writeEvent.[[NoTear]]true
        1. 如果 readEventwriteEvent 具有相等的内存范围,并且存在 Memory event value,使得 valuewriteEvent 具有相等的内存范围value.[[NoTear]]truewriteEventvalue 不是同一个 Shared Data Block event,且 readEventexecutionreads-from value,则
          1. 返回 false
  2. 返回 true
Note

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

直观地说,此要求表示,当通过整数 TypedArray 以对齐方式访问某个内存范围时,在与具有相等范围的其他 write event数据竞争中,必须有一个该范围上的单一 write event “胜出”。更精确地说,此要求表示,对齐的 read event 不能读取由多个不同 write event 的字节组成的值,而这些 write event 全都具有相等范围。不过,对齐的 read event 可以从多个具有重叠范围的 write event 中读取。

29.7.4 顺序一致的原子操作

对于 candidate execution executionis-memory-order-beforeEventSet(execution) 中所有 Memory event 的严格全序,并满足以下条件。

如果 candidate execution 允许存在 is-memory-order-before 关系,则它具有顺序一致的原子操作。

Note 3

虽然 is-memory-order-before 包含 EventSet(execution) 中的所有事件,但在 execution 中不受 happens-beforesynchronizes-with 约束的事件被允许出现在该排序中的任何位置。

29.7.5 有效执行

如果以下全部为真,则 candidate execution execution 是有效执行(或简称 execution)。

  • 宿主execution 提供 host-synchronizes-with 关系。
  • execution 允许存在作为严格偏序happens-before 关系。
  • execution 具有有效的 chosen read。
  • execution 具有相干读。
  • execution 具有无撕裂读。
  • execution 具有顺序一致的原子操作。

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

29.8 竞争

对于 execution execution,以及包含SharedDataBlockEventSet(execution) 中的事件 eventAeventB,如果以下算法返回 true,则 eventAeventB 处于竞争

  1. 如果 eventAeventB 不是同一个 Shared Data Block event,则
    1. 如果并非 eventAexecutionhappens-before eventBeventBexecutionhappens-before eventA,则
      1. 如果 eventAWriteSharedMemoryReadModifyWriteSharedMemory event,eventBWriteSharedMemoryReadModifyWriteSharedMemory event,并且 eventAeventB 没有不相交的内存范围,则
        1. 返回 true
      2. 如果 eventAexecutionreads-from eventB,或 eventBexecutionreads-from eventA,则
        1. 返回 true
  2. 返回 false

29.9 数据竞争

对于 execution execution,以及包含SharedDataBlockEventSet(execution) 中的事件 eventAeventB,如果以下算法返回 true,则 eventAeventB 处于数据竞争

  1. 如果 eventAeventBexecution 中处于 竞争,则
    1. 如果 eventA.[[Order]] 不是 seq-csteventB.[[Order]] 不是 seq-cst,则
      1. 返回 true
    2. 如果 eventAeventB 具有重叠的内存范围,则
      1. 返回 true
  2. 返回 false

29.10 数据竞争自由

如果 execution execution 中不存在两个在 SharedDataBlockEventSet(execution) 中且处于数据竞争的事件,则 execution数据竞争自由的。

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

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

29.11 共享内存指南

Note 1

以下是针对使用共享内存的 ECMAScript 程序员的指南。

我们建议保持程序数据竞争自由,即,使得不可能在同一内存位置上存在并发的非原子操作。数据竞争自由程序具有交错语义,其中每个 agent 的求值语义中的每一步彼此交错。对于数据竞争自由程序,没有必要理解内存模型的细节。这些细节不太可能建立起有助于更好编写 ECMAScript 的直觉。

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

Note 2

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

最好允许大多数在单 agent 设置中有效的程序转换在多 agent 设置中也有效,以确保多 agent 程序中每个 agent 的性能与在单 agent 设置中一样好。这些转换通常很难判断。我们概述了一些关于程序转换的规则,这些规则意图作为规范性规则(因为它们由内存模型蕴含,或强于内存模型所蕴含的内容),但可能并不详尽。这些规则意图适用于那些发生在构成 is-agent-order-before 关系的 Memory event 被引入之前的程序转换。

agent-order slice 为与单个 agent 相关的 is-agent-order-before 关系的子集。

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

在没有共享内存的情况下对 agent-order slice 有效的任何转换,在存在共享内存的情况下也是有效的,但有以下例外。

  • 原子操作刻在石头上:程序转换不得导致任何 [[Order]]seq-cstShared Data Block eventis-agent-order-before 关系中移除,也不得导致它们相互重排序,或在 agent-order slice 内与 [[Order]]unordered 的事件重排序。

    (实践中,对重排序的禁止会迫使编译器假定每个 seq-cst 操作都是同步,并包含在最终的 is-memory-order-before 关系中;在没有跨 agent 程序分析的情况下,它通常也必须这样假定。它还会迫使编译器假定每个 callee 对 memory-order 的影响未知的调用都可能包含 seq-cst 操作。)

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

    (例如,如果程序中语义上单个读被执行多次,则随后允许程序只观察到所读取值中的一个。称为 rematerialization 的转换可能违反此规则。)

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

    (例如,转换不得引入某些可观察写,例如通过在较大位置上使用读-改-写操作来写入较小数据、向内存写入程序不可能写入的值,或将刚读取的值写回其读取位置,而该位置在读取之后可能已被另一个 agent 覆写。)

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

    (反直觉地,此规则实际上限制了对写的转换,因为写在内存模型中具有作用力,就其能被 read event 读取而言。例如,写可以在两个 seq-cst 操作之间移动、合并,有时也可重排序,但转换不得移除每个更新某个位置的写;必须保留某个写。)

仍然有效的转换示例包括:合并来自同一位置的多个非原子读、重排序非原子读、引入推测性非原子读、合并对同一位置的多个非原子写、重排序对不同位置的非原子写,以及即使影响终止也将非原子读提升出循环。通常要注意,别名化的 TypedArray 会使证明位置不同变得困难。

Note 3

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

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

具体而言,内存模型意图允许如下代码生成。

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

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

  • 常规 load 和 store 编译为单个 load 和 store 指令。
  • 无锁原子 load 和 store 编译为完整(顺序一致)栅栏、常规 load 或 store,以及完整栅栏。
  • 无锁原子读-改-写访问编译为完整栅栏、原子读-改-写指令序列,以及完整栅栏。
  • 非无锁原子操作编译为获取 spinlock、完整栅栏、一系列非原子 load 和 store 指令、完整栅栏,以及释放 spinlock。

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

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

  • 存在明显的平台相关改进,可以移除冗余栅栏。例如,在 x86 上,无锁原子 load 和 store 周围的栅栏总是可以省略,除了 store 之后的栅栏;并且无锁读-改-写指令不需要栅栏,因为这些都使用带 LOCK 前缀的指令。在许多平台上存在几种强度的栅栏,并且可以在某些上下文中使用较弱栅栏而不破坏顺序一致性。
  • 大多数现代平台都支持 ECMAScript 原子操作所需所有数据大小的无锁原子操作。如果需要非无锁原子操作,围绕原子操作主体的栅栏通常可以折叠到加锁和解锁步骤中。对于非无锁原子操作,最简单的解决方案是每个 SharedArrayBuffer 使用一个锁字。
  • 还存在更复杂的平台相关局部改进,需要一些代码分析。例如,两个背靠背栅栏通常与单个栅栏具有相同效果,因此如果为连续的两个原子操作生成代码,只需用单个栅栏分隔它们。在 x86 上,甚至可以省略分隔原子 store 的单个栅栏,因为 store 之后的栅栏只用于将该 store 与后续 load 分隔开。