Tob*_*ull 4 c++ concurrency memory-model c++11 stdatomic
与我之前的问题类似,请考虑以下代码
-- Initially --
std::atomic<int> x{0};
std::atomic<int> y{0};
-- Thread 1 --
x.store(1, std::memory_order_release);
-- Thread 2 --
y.store(2, std::memory_order_release);
-- Thread 3 --
int r1 = x.load(std::memory_order_acquire); // x first
int r2 = y.load(std::memory_order_acquire);
-- Thread 4 --
int r3 = y.load(std::memory_order_acquire); // y first
int r4 = x.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
是怪异的结果 r1==1, r2==0,并r3==2, r4==0有可能在C ++ 11内存模型下,这种情况下?如果我要全部替换std::memory_order_acq_rel成该std::memory_order_relaxed怎么办?
在x86上,这样的结果似乎是被禁止的,请参见此SO问题,但我一般是在询问C ++ 11内存模型。
奖励问题:
我们都同意,与std::memory_order_seq_cst该怪异的结果不会在C ++ 11被允许。现在,赫伯·萨特(Herb Sutter)在他著名的- atomic<>武器谈话中 @ 42:30说std::memory_order_seq_cst,这就像- std::memory_order_acq_rel 但 std::memory_order_acquire -loads在std::memory_order_release-writes 之前可能不会移动。我看不到上面示例中的此附加约束如何防止奇怪的结果。谁能解释?
问题中更新的1代码( 在线程 4 中加载x和y交换了 1 个)实际上测试了所有线程是否同意全局存储顺序。
在 C++11 内存模型下,结果r1==1, r2==0, r3==2, r4==0是允许的,实际上在 POWER 上是可观察到的。
在 x86 上,这种结果是不可能的,因为“其他处理器以一致的顺序看到存储”。在顺序一致执行中也不允许这种结果。
注:1:这个问题原本有两个读者阅读x,然后y。甲顺序一致的执行是:
-- Initially --
std::atomic<int> x{0};
std::atomic<int> y{0};
-- Thread 4 --
int r3 = x.load(std::memory_order_acquire);
-- Thread 1 --
x.store(1, std::memory_order_release);
-- Thread 3 --
int r1 = x.load(std::memory_order_acquire);
int r2 = y.load(std::memory_order_acquire);
-- Thread 2 --
y.store(2, std::memory_order_release);
-- Thread 4 --
int r4 = y.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
这导致r1==1, r2==0, r3==0, r4==2. 因此,这根本不是一个奇怪的结果。
为了能够说每个读者看到不同的商店订单,我们需要他们以相反的顺序阅读以排除最后一个商店只是被延迟。
这种重新排序测试称为IRIW(独立读者,独立作家),我们在此检查两个读者是否可以看到同一对商店以不同的顺序出现。相关,可能重复:具有4个线程的获取/释放语义
正如@MWid的回答所说,非常弱的C ++ 11内存模型不需要所有线程就存储的全局顺序达成一致。
该答案将说明一种可能的硬件机制,该机制可能导致线程对存储的全局顺序不一致,这在为无锁代码设置测试时可能是相关的。而且,如果您喜欢cpu-architecture 1,这很有趣。
有关这些ISA的抽象模型,请参见《 ARM和POWER松弛内存模型教程简介》:ARM和POWER都不保证所有线程都能看到一致的全局存储顺序。 实际上,在POWER芯片上可以观察到这一点,而在ARM上理论上可以观察到,但是在任何实际的实现上都可以观察到。
(我认为像Alpha这样的其他弱排序的ISA 也允许这种重新排序。ARM曾经允许在纸上进行这种重新排序,但是可能没有真正的实现方式进行了这种重新排序。ARMv8甚至增强了他们的在纸模型,甚至对于将来的硬件也不允许这样做。 )
在计算机科学中,一台机器的含义是:存储对所有其他线程同时可见(因此存在一个单一的全局存储顺序),它是“ 多副本原子 ”或“多副本原子”。x86和SPARC的TSO内存模型具有该属性,但是ARM和POWER不需要它。
当前的SMP机器使用MESI来维护单个一致的缓存域,以便所有内核具有相同的内存视图。当存储从存储缓冲区提交到L1d高速缓存中时,存储将变得全局可见。届时,其他任何核心的负载都将看到该存储。还有就是致力于缓存所有商店的一个订单,因为MESI保持一个相关域。如果有足够的障碍来停止本地重新排序,则可以恢复顺序一致性。
在全局可见之前,存储可以对某些但不是所有其他核心可见。
POWER CPU使用同步多线程(SMT)(超线程的通用术语)在一个物理内核上运行多个逻辑内核。我们关心的内存排序规则是针对运行线程的逻辑核心,而不是物理核心。
我们通常认为加载是从L1d中获取它们的值,但是从同一内核重新加载最近的存储并且数据直接从存储缓冲区转发时,情况并非如此。(存储到装载转发或SLF)。甚至有可能获得一个负载,该值永远不会出现在L1d中,并且即使在强排序的x86上也不会出现带有部分SLF的值。(请参阅有关全局不可见负载说明的答案)。
存储缓冲区在存储指令退出之前跟踪推测性存储,但还在非推测性存储从内核的无序执行部分退出(ROB / ReOrder Buffer)退出之后,对非推测性存储进行缓冲。
同一物理核心上的逻辑核心共享一个存储缓冲区。投机(尚未退休)存储必须对每个逻辑核心保持私有。(否则,这将使他们的推测结合在一起,并且如果检测到错误推测,则两者都需要回滚。这将无法实现SMT的部分目的,即在一个线程停止运行时使核心保持繁忙或从分支的错误预测中恢复过来) 。
但是我们可以让其他逻辑内核监听非推测性存储的存储缓冲区,这些存储肯定会最终提交给L1d缓存。在它们出现之前,其他物理核心上的线程看不到它们,但是共享同一物理核心的逻辑核心可以看到它们。
(我不确定这是否是硬件机制,它使POWER产生这种怪异现象,但这是合理的)。
这种机制使存储对于SMT兄弟核心是可见的,然后才对所有核心全局可见。但是它仍然是核心内部的,因此可以通过仅影响存储缓冲区的障碍来廉价地避免这种重新排序,而无需实际强制核心之间进行任何缓存交互。
(ARM / POWER论文中提出的抽象内存模型对此建模,因为每个内核都有自己的缓存视图,并且缓存之间的链接可以使它们同步。但是在典型的现代物理硬件中,我认为唯一的机制是SMT兄弟之间,而不是在单独的核心之间。)
请注意,x86根本不允许其他逻辑内核监听存储缓冲区,因为这会违反x86的TSO内存模型(通过允许这种奇怪的重新排序)。作为我的回答,将在使用HT的一个Core上执行什么线程间的数据交换?解释说,具有SMT的Intel CPU(Intel称为超线程)在逻辑内核之间静态划分存储缓冲区。
脚注1:您真正需要知道的所有关于内存排序的C ++或特定ISA上的asm的抽象模型。
不需要了解硬件细节(并且可能导致您陷入无法思考的陷阱,因为您无法想象实现它的机制)。
| 归档时间: |
|
| 查看次数: |
540 次 |
| 最近记录: |