是否会有其他线程以相同的顺序看到两个原子写入不同线程中的不同位置?

时间:2015-01-06 21:01:50

标签: c++ c++11 concurrency memory-model stdatomic

与我的previous问题类似,请考虑此代码

-- 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);

在这种情况下,奇怪的结果 r1==1, r2==0r3==2, r4==0是否可能在C ++ 11内存模型下?如果我要将所有std::memory_order_acq_rel替换为std::memory_order_relaxed

,该怎么办?

在x86上这样的结果似乎是被禁止的,请参阅this SO question,但我一般都在询问C ++ 11内存模型。

加分问题:

我们都同意,在{+ 1}}中,C ++ 11中不允许使用奇怪的结果。现在,Herb Sutter在他着名的atomic<>-weapons talk @ 42:30说std::memory_order_seq_cst就像std::memory_order_seq_cst 但是 std::memory_order_acq_rel - 载荷可能不会移动std::memory_order_acquire - 写入。我无法看到上述示例中的这个附加约束如何阻止奇怪的结果。谁能解释一下?

4 个答案:

答案 0 :(得分:4)

简短的回答是否定的。标准并没有说它们必须是,因此它们不一定是。无论你能否想象出一种特定的方式,都无关紧要。

答案 1 :(得分:4)

问题中更新的 1 代码(在线程4中交换xy的负载)确实测试所有线程是否同意全局商店订单。

在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);

这导致r1==1, r2==0, r3==0, r4==2。因此,这根本不是 一个奇怪的结果。

为了能够说每个读者看到不同的商店订单,我们需要他们以相反的顺序阅读,以排除最后一家商店的延迟。

答案 2 :(得分:2)

非常弱的C ++ 11内存模型并不要求所有线程都同意商店的全球订单,正如@ MWid的答案所说。

这个答案将解释一种可能导致线程不同意商店全局顺序的硬件机制,这在设置无锁代码测试时可能是相关的。只是因为你喜欢cpu-architecture 1

请参阅A Tutorial Introduction to the ARM and POWER Relaxed Memory Models了解这些ISA的抽象模型:ARM和POWER都不保证所有线程都能看到一致的全局存储顺序。 实际上在POWER芯片的实践中可以观察到这一点,并且理论上可能在ARM上可能实现,但可能没有任何实际的实现。

其他弱排序的ISA like Alpha也允许这种重新排序,我认为.ARM只是ISA的一个例子,允许它在纸上,但可能没有真正的实现这样做重新排序。)

在计算机科学中,商店对所有其他线程同时可见的机器(因此存在单个全局商店订单)的术语是“ multiple-copy atomic ” 。 x86和SPARC的TSO内存模型具有该属性,但ARM和POWER不需要它。

当前的SMP计算机使用MESI来维护单个一致的缓存域,以便所有核心具有相同的内存视图。当存储从存储缓冲区提交到L1d缓存时,存储将变为全局可见。此时,来自任何其他核心的负载将看到该存储。 所有提交缓存的商店的单个订单,因为MESI维护单个一致性域。有足够的障碍来阻止本地重新排序,可以恢复顺序一致性。

POWER CPU使用 Simultaneous MultiThreading (SMT) (超线程的通用术语)在一个物理核心上运行多个逻辑核心。我们关心的内存排序规则是针对线程运行的逻辑内核而不是物理内核。

我们通常认为负载是从L1d获取它们的值,但是当从同一个核心重新加载最近的存储并且数据直接从存储缓冲区转发时,情况并非如此。 (存储到转发转发或SLF)。甚至在具有部分SLF的强排序x86上,负载甚至可能获得L1d中永远不会出现的值。 (参见我对Globally Invisible load instructions的回答)。

存储缓冲区在存储指令退出之前跟踪推测存储,但是在从核心的无序执行部分(ROB / ReOrder缓冲区)退出之后还缓冲非推测存储。

同一物理内核上的逻辑内核共享一个存储缓冲区。投机(尚未退休)的商店必须保持对每个逻辑核心的私密性。 (否则,如果检测到错误推测,那么它们会将他们的推测结合在一起并要求两者都回滚。这将破坏SMT的部分目的,在一个线程停止或从分支错误预测中恢复时保持核心忙“)

但是我们可以让其他逻辑核心窥探存储缓冲区,这些非推测性存储肯定会最终提交到L1d缓存。在他们这样做之前,其他物理核心上的线程看不到它们,但是共享相同物理核心的逻辑核心可以。

(我不确定这究竟是什么硬件机制允许POWER上的这种奇怪,但它似乎是合理的。)

此机制使存储在SMT兄弟核心对所有核心全局可见之前可见。但它仍然在核心内部本地,因此可以通过仅影响存储缓冲区的障碍来廉价地避免这种重新排序,而不会实际强制核心之间的任何缓存交互。

(ARM / POWER论文中提出的抽象内存模型对此进行建模,因为每个内核都有自己的内存缓存视图,缓存之间的链接可以让它们同步。但在典型的物理现代硬件中,我认为唯一的机制是在SMT兄弟之间,而不是在单独的核心之间。)

请注意,x86根本不允许其他逻辑核心窥探存储缓冲区,因为这会违反x86的TSO内存模型(通过允许这种奇怪的重新排序)。正如我在What will be used for data exchange between threads are executing on one Core with HT?上的回答所解释的那样,带有SMT的英特尔CPU(英特尔称之为超线程)在逻辑内核之间静态分区存储缓冲区。

脚注1:C ++的抽象模型,或者特定ISA上的asm的抽象模型,是你真正需要知道的关于内存排序的理由。

理解硬件细节不是必要的(并且可能会导致你陷入一种思考某些事情是不可能的陷阱,因为你无法想象它的机制)。

答案 3 :(得分:1)

  

在这种情况下,奇怪的结果 r1==1, r2==0r3==0, r4==2是否可能在C ++ 11内存模型下?

是。 C ++内存模型允许这样的奇怪的结果

  

如果我要用std::memory_order_acq_rel替换所有std::memory_order_relaxed怎么办?

如果您按memory_order_acquire替换所有memory_order_releasememory_order_relaxed,则代码没有任何更改。

  

std::memory_order_seq_cst就像std::memory_order_acq_relstd::memory_order_acquire - 加载可能会在std::memory_order_release之前移动 - 写入。   我无法看到上述示例中的这个附加约束如何阻止奇怪的结果

acquire - 加载可能会在release之前移动 - 写入。”显示了顺序一致性约束的一个方面(memory_order_seq_cst)。

在C ++内存模型中,它只保证seq_cst具有acq_rel语义,而所有 seq_cst原子访问只有一些“总命令”不再存在减。当存在这样的“总排序”时,我们无法得到奇怪的结果,因为所有seq_cst原子访问都像在单个线程上的任何交错顺序中一样执行。

您的previous question对待原子变量的“一致性”,此问题询问所有原子变量的“一致性”。 C ++内存模型保证单个原子变量的直观一致性,即使是最弱的排序(relaxed),也可以保证不同原子变量的“顺序一致性”,只要默认排序(seq_cst)。 当您明确使用非seq_cst排序原子访问时,正如您所指出的那样,这可能是奇怪的结果。