memory_order_relaxed 和知名度
memory_order_relaxed and visibility
考虑两个线程 T1 和 T2,它们分别存储和加载原子整数 a_i。让我们进一步 假设 存储在 在 加载开始执行之前执行。之前,我指的是绝对时间意义上的。
T1 T2
// other_instructions here... // ...
a_i.store(7, memory_order_relaxed) // other instructions here
// other instructions here // ...
a_i.load(memory_order_relaxed)
// other instructions here
是否保证T2加载后看到的是值7?
Is it guaranteed that T2 sees the value 7 after the load?
此处与内存顺序无关;原子操作是 atomic。只要您确保写入“先于”读取(您在问题的前提下声明为真),并且没有其他干预操作,T2 就会读取 T1 写入的值。这是原子操作的本质,内存顺序不会修改这个。
内存指令控制是什么如果T2看到7(是否保证“happens-before”),是否可以访问other数据被T1修改之前它将7存储到原子中。对于 relaxed
内存排序,T2 没有这样的保证。
注意:您将问题从 load "happens after" the store, when the store is explicitly "synchronized" with the load 的情况更改为更加模糊的情况。就 C++ 对象模型而言,没有“绝对时间”。特定原子对象上的所有原子操作都按顺序发生,但除非有某些东西 显式 在两个负载之间创建“发生 before/after” 关系,否则会得到什么值加载无法知道。会是两种可能中的一种,不知道是哪一种。
(我正在回答 updated 问题;Nicol 回答了原始问题,该问题在 C++“发生之前”术语中指定了“之后”,包括同步,这意味着reader 保证能看到作者所做的事情。并不是说它们 运行 处于锁步循环循环中;C++ 没有任何“循环”的概念。)
我正在回答 C++ 如何在普通的现代 CPU 上运行。 ISO C++ 当然对 CPU 架构只字未提,只是在关于 C++ 标准中 atomic<>
一致性保证目的的说明中提到普通硬件具有一致性缓存。
By before, I mean in the absolute sense of time.
如果您的意思是商店在加载执行之前变得全局可见,那么根据定义加载将会看到它。但是,如果您指的是普通计算机体系结构意义上的“执行”,那么 不,不能保证。如果商店同时 运行 在不同的内核上,它们需要一些时间才能对其他线程可见。
Modern CPUs ,因此执行可以是推测性的和无序的执行,而不会在核心外部可见混乱,因此执行不必在缓存上停止-错过商店。缓存是连贯的;您无法从中读取“陈旧”值,但是商店需要一些时间才能对其他核心可见。 (在计算机体系结构术语中,存储通过将数据+地址写入存储缓冲区来“执行”。当它从存储缓冲区提交到 L1d 缓存时,它在已知为非推测性之后变得全局可见。)
内核在修改缓存行之前需要获得其独占所有权(MESI 独占或修改状态),因此如果它还没有拥有该行,它将发送一个 RFO(读取所有权)当它需要将存储从存储缓冲区提交到 L1d 缓存时。在核心看到 RFO 之前,它可以继续让负载读取该行(即“执行”负载 - 请注意,加载和存储在高性能 CPU 中是根本不同的,核心希望尽早加载数据可能,但开店晚了)。
相关:如果线程 1 也做了一些稍后的加载,存储缓冲区也是您获得 StoreLoad 重新排序的方式,即使在保持其他一切有序的强排序 CPU 上也是如此。或者在 CPU 上使用像 x86 这样的强顺序内存模型,它保持一切都按程序顺序发生的错觉,除了存储缓冲区。
内存屏障只是命令这个核心的操作。彼此,例如一个完整的屏障会阻止后面的加载执行,直到前面的存储+加载已经执行并且存储缓冲区已经耗尽到屏障的点,所以它只包含后面的加载(如果有的话)。
障碍对另一个核心是否看到商店没有影响,除非前提条件是另一个核心已经看到一些其他 商店。然后使用障碍(或等同于 release/acquire),您可以保证另一个核心也能看到发布存储之前的所有其他内容。
Jeff Preshing 的 mental model of memory operations as source-control operations 访问远程服务器是一个有用的模型:您可以相对于彼此订购您自己的操作,但是来自不同内核的管道中的请求可以以不同的顺序到达服务器(共享内存)。
这就是为什么 C++ 仅将可见性指定为“最终”/“及时”,如果您已经看到(通过获取加载)来自发布存储的值,则可以保证看到更早的内容。 (“迅速”的含义取决于硬件。在现代多核系统上通常低于 100 ns(取决于您测量的具体内容),尽管多插槽可能更慢。)
自己看店(发布,seq_cst,不需要同步其他的就更轻松了loads/stores)要么发生要么不发生,这就是在线程之间创建before/after概念的原因。由于 CPUs 只能通过共享内存(或处理器间中断)看到彼此的操作,因此没有太多好的方法来建立任何同时性的概念。与物理学非常相似,相对论使得很难说两件事同时发生,如果它们没有发生在同一个地方:这取决于观察者,因为能够看到任一事件的延迟。
(在现代 x86 之类的机器上,TSC 内核之间同步(这在单插槽多核系统中很常见,而且显然也是大多数(?)多插槽主板) , 你实际上可以找到绝对时间戳来确定哪个内核在什么时候执行什么,但是无序执行仍然是一个很大的混杂因素。流水线 CPUs 使得很难准确地说出任何给定指令何时“执行” . 而且由于通过内存进行的通信不是零延迟,因此即使尝试以这种方式建立同时性通常也没有用。)
考虑两个线程 T1 和 T2,它们分别存储和加载原子整数 a_i。让我们进一步 假设 存储在 在 加载开始执行之前执行。之前,我指的是绝对时间意义上的。
T1 T2
// other_instructions here... // ...
a_i.store(7, memory_order_relaxed) // other instructions here
// other instructions here // ...
a_i.load(memory_order_relaxed)
// other instructions here
是否保证T2加载后看到的是值7?
Is it guaranteed that T2 sees the value 7 after the load?
此处与内存顺序无关;原子操作是 atomic。只要您确保写入“先于”读取(您在问题的前提下声明为真),并且没有其他干预操作,T2 就会读取 T1 写入的值。这是原子操作的本质,内存顺序不会修改这个。
内存指令控制是什么如果T2看到7(是否保证“happens-before”),是否可以访问other数据被T1修改之前它将7存储到原子中。对于 relaxed
内存排序,T2 没有这样的保证。
注意:您将问题从 load "happens after" the store, when the store is explicitly "synchronized" with the load 的情况更改为更加模糊的情况。就 C++ 对象模型而言,没有“绝对时间”。特定原子对象上的所有原子操作都按顺序发生,但除非有某些东西 显式 在两个负载之间创建“发生 before/after” 关系,否则会得到什么值加载无法知道。会是两种可能中的一种,不知道是哪一种。
(我正在回答 updated 问题;Nicol 回答了原始问题,该问题在 C++“发生之前”术语中指定了“之后”,包括同步,这意味着reader 保证能看到作者所做的事情。并不是说它们 运行 处于锁步循环循环中;C++ 没有任何“循环”的概念。)
我正在回答 C++ 如何在普通的现代 CPU 上运行。 ISO C++ 当然对 CPU 架构只字未提,只是在关于 C++ 标准中 atomic<>
一致性保证目的的说明中提到普通硬件具有一致性缓存。
By before, I mean in the absolute sense of time.
如果您的意思是商店在加载执行之前变得全局可见,那么根据定义加载将会看到它。但是,如果您指的是普通计算机体系结构意义上的“执行”,那么 不,不能保证。如果商店同时 运行 在不同的内核上,它们需要一些时间才能对其他线程可见。
Modern CPUs
内核在修改缓存行之前需要获得其独占所有权(MESI 独占或修改状态),因此如果它还没有拥有该行,它将发送一个 RFO(读取所有权)当它需要将存储从存储缓冲区提交到 L1d 缓存时。在核心看到 RFO 之前,它可以继续让负载读取该行(即“执行”负载 - 请注意,加载和存储在高性能 CPU 中是根本不同的,核心希望尽早加载数据可能,但开店晚了)。
相关:如果线程 1 也做了一些稍后的加载,存储缓冲区也是您获得 StoreLoad 重新排序的方式,即使在保持其他一切有序的强排序 CPU 上也是如此。或者在 CPU 上使用像 x86 这样的强顺序内存模型,它保持一切都按程序顺序发生的错觉,除了存储缓冲区。
内存屏障只是命令这个核心的操作。彼此,例如一个完整的屏障会阻止后面的加载执行,直到前面的存储+加载已经执行并且存储缓冲区已经耗尽到屏障的点,所以它只包含后面的加载(如果有的话)。
障碍对另一个核心是否看到商店没有影响,除非前提条件是另一个核心已经看到一些其他 商店。然后使用障碍(或等同于 release/acquire),您可以保证另一个核心也能看到发布存储之前的所有其他内容。
Jeff Preshing 的 mental model of memory operations as source-control operations 访问远程服务器是一个有用的模型:您可以相对于彼此订购您自己的操作,但是来自不同内核的管道中的请求可以以不同的顺序到达服务器(共享内存)。
这就是为什么 C++ 仅将可见性指定为“最终”/“及时”,如果您已经看到(通过获取加载)来自发布存储的值,则可以保证看到更早的内容。 (“迅速”的含义取决于硬件。在现代多核系统上通常低于 100 ns(取决于您测量的具体内容),尽管多插槽可能更慢。
自己看店(发布,seq_cst,不需要同步其他的就更轻松了loads/stores)要么发生要么不发生,这就是在线程之间创建before/after概念的原因。由于 CPUs 只能通过共享内存(或处理器间中断)看到彼此的操作,因此没有太多好的方法来建立任何同时性的概念。与物理学非常相似,相对论使得很难说两件事同时发生,如果它们没有发生在同一个地方:这取决于观察者,因为能够看到任一事件的延迟。
(在现代 x86 之类的机器上,TSC 内核之间同步(这在单插槽多核系统中很常见,而且显然也是大多数(?)多插槽主板) , 你实际上可以找到绝对时间戳来确定哪个内核在什么时候执行什么,但是无序执行仍然是一个很大的混杂因素。流水线 CPUs 使得很难准确地说出任何给定指令何时“执行” . 而且由于通过内存进行的通信不是零延迟,因此即使尝试以这种方式建立同时性通常也没有用。)