// Thread 1:
x.store(1, std::memory_order_seq_cst); // A
y.store(1, std::memory_order_release); // B
// Thread 2:
r1 = y.fetch_add(1, std::memory_order_seq_cst); // C
r2 = y.load(std::memory_order_relaxed); // D
// Thread 3:
y.store(3, std::memory_order_seq_cst); // E
r3 = x.load(std::memory_order_seq_cst); // F
字符串
允许产生r1 == 1 && r2 == 3 && r3 == 0,其中A发生在C之前,但C在memory_order_seq_cst的单个总顺序C-E-F-A中先于A(参见Lahav等人)。
因为我是新手,最近看内存模型的时候一直很困惑。请问如何看待“A发生在C之前”这句话?难道我们不应该认为不同线程之间的happens-before应该有一个while循环来进行同步吗?这里的上下文是否意味着如果C碰巧被加载到存储在A中的值(但它可能不会被加载,因为它不是一个while循环)?另外,如何理解A发生在之前。C,但C在单个总顺序中先于A。从线程B的Angular 来看,A发生在C之前,那么A执行C的结果是可见的,为什么C先于A出现呢?
1条答案
按热度按时间gcuhipw91#
以下内容使用了多层次的降价商品列表,在移动的设备上可能难以查看。
最近我也读了同样的cppreference部分,也有和你一样的问题,在看了相关的SO QA和论文后,我有了这个想法。我希望这个答案也能帮助你理解cppreference是怎么说的。
您的问题似乎是QA_1和QA_2的 * 副本 ,但两个QA似乎都没有读取cppreference_1和paper_2中引用的原始paper_1。所以在这里,我将主要根据论文_1,部分根据其参考文献paper_3,从数学的Angular 给予一个答案,如果有数学基础知识,可能有助于把握其内在思想。
这些都是基于我对论文的理解和相关链接。 如有错误,请指出 *,谢谢。
简答:
1.查看QA_1问题中的引用,您将获得基于旧c++11标准的上述冲突发生的场合。
QA_1回答说这在真实的世界中是如何发生的(可能是由于该高速缓存一致性导致不同线程看到不同顺序的不同变量)。
S(k, m)
B与(sw)C关于变量
y
而不是关于x
同步,因此A发生在C之前。(这是“发生在”之前的定义:由sb和sw组成的序列)S(m, o, p, k)
(这里我按顺序合并了不同的S(,)
)。S(p,k)
是由于“reads-before”(IMO,这意味着“reads-before-write”是由于x1c 0d1x。这是为了解决战争的危险)。S(k, m)
和S(m, o, p, k)
,其中前者保持“happens-before”,后者是新的总修改顺序。(A)-B-C-E-D-F-A
(这里的“(A)”表示它在之前运行,但在之后观察到)。注意:* 也许 * 上面在c++20中引入的优化内存模型仍然有缺陷。然而,我不是一个编译器/计算机体系结构Maven,所以找到缺陷超出了我的能力。
详细解答主要基于论文_1:
下面需要一些离散数学的知识,我为暂时不熟悉的人添加一些描述。
如果你不想被“数学”卡住,那么查看下面的“非数学”部分就足够了。
注意:这里只对一些符号进行了重新表述,如果对某些术语有疑问,最好查看原文。
下面的部分原始符号定义(主要是关于关系的)可以从paper_1中的“Notation 1”和paper_1中引用的like
(;)
paper_3中的“Definition 8”中看到。我们在这里使用Batty等人的版本的原因是它为我们的讨论提供了一个更清晰的起点,我们对C11的SC语义问题的解决方案将建立在它的基础上。
在查看p5中的paper_3脚注后,它主要基于ISO标准,并将其Map到 * 纯数学 *,如果有更好的数学知识,可能会更直观。
(
x
的意思。)这里
;
是关系的组合(即表示一个流水线,如
[A] -> R -> [B]
)。这意味着一个AB对具有关系R。
A在B之前是依赖性排序的,或者
这意味着包括consume。
除了新的SC和NO -THIN - AIR条件外,RC 11与C11在其他几个方面有所不同。不支持消耗访问
因此,上面的cppreference定义可能比paper_1中的以下数学定义更通用。
的
接下来,我们说一个事件发生在另一个事件之前(hb),如果它们由**
sb
或sw
边序列连接**+
表示可传递(即sequence),U
表示“或”。由于
S(l,m)
同步,上述S(k,m)
具有“happens-before”关系(这是由于l,m
具有隐含的数据依赖性(即,我在读我写的sw
的定义如cppreference_1的其他部分所述,将其视为
rel
,acq
序列。如果线程A中的原子存储是release操作,线程B中的原子load从相同变量是acquire操作,线程B中的load读取线程A中的存储写入的值,则线程A中的存储与线程B中的加载同步。
x1c4d 1x型
接下来,释放事件a与获取事件B同步(sw),每当b(或,在b是栅栏的情况下,一些sb-prior读取)从a(或在a是栅栏的情况下,一些sb-later写入)的释放序列读取时。
?
意味着“或”关系。表示偏序,所以
包含了论文_1第7页右下图中的
sc
。[F]
总是显式地放置在weak架构中,如paper_1中引用的POWERPC,它与rel
或acq
等相关(有关编译器如何添加这些围栏,请参阅paper_2中的link)。rs; rf
由rel
和acq
表示是考虑RAR(读后读)。
rs
的定义在
rs
的定义中,rf; rmw
是为了得到类似“写,读,写”的序列。*综上所述,以上表示rel_event,(fence-sync),release_relation,read-from,relaxed_read,(fence-sync),acq_event
rel
和acq
之间的关系。sb
的定义。在这里,我采用paper_3中的定义来强调它们在同一线程中。来自cppreference_2:
Sequenced before是同一线程内求值之间的非对称、传递、成对关系。
型
sb
表示:来自同一线程的事件之间的严格偏序(即非自反和传递),它捕获程序顺序。
thd
:I
:一组初始事件。每个初始事件
是一个非原子写零;即kind(e)= Wna和wval(e)= 0。此外,每个位置正好有一个初始事件。
S
_1)正如paper_1所说:
在所有SC事件上应该有严格的总顺序S,直观地对应于这些事件被执行的顺序。
S关系以严格总计顺序,所有且仅执行中的SC事件相关;即,
型
id
是“恒等关系”意味着
S
命令是运行时定义的。acy(S)
(S是非循环的)是重要的,因为这导致了上面的示例失败。(S1)S必须包括仅限于SC事件的hb
(formally:
);
[...](这些与论文未发现的问题保持一致)
非数学:
S
需要符合hb(happens-before)顺序数学:见上面的等式。
因为它drops of
sync
fences.然后它暗示了weaker内存模型。相反,如果我们遵循前导同步约定,则hb路径(图1B中)将被移除。3)从k到m,以sw边缘结束,避免了放置在m之前的围栏。
参见paper_2:
另一方面,Power实现允许这种结果。对于顺序一致的原子,Power通常使用“前导栅栏”约定。(参见http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html)这意味着在线程1的指令之间只有一个lwsync围栏。这不具有“累积性”/传递性属性,在这些情况下,将存储到x对线程3可见。
(the上面的引用
isync
可能在最后使用,以确保正确的上下文并避免refetching的“进程内重新排序”。我从来没有编写过POWERPC,所以我只提供了相关的参考链接,这不是我的经验)。上面的2个引号的意思是关于同步围栏的下降是如何发生的。(即
hwsync
通过sc
的“Store Seq Cst”被避免,并且仅存在lwsync
)论文_1:
所以,如果要求hb(在SC事件上)包含在S中是太强的条件,我们应该要求什么呢?
基本的见解是,根据任一编译方案,我们知道如果从a到b的hb路径以sb边缘开始和结束,则在SC访问a和b之间必然存在同步栅栏。第二,如果a和B访问相同的位置,那么硬件无论如何都会保留顺序。
参见上文“[F]”关于同步围栏的内容。
(S1 fix)S必须关联通过hb关联的任何两个SC事件,前提是两个事件之间的hb路径要么以sb边缘开始和结束,要么以访问相同位置开始和结束
sb
(因为这不会导致程序顺序的循环)现在在c++20中被允许,而不是整个hb
(happens-before)。(形式上:
,其中
表示对相同位置的访问之间的HB边)。
hb
->sb...
(即现在在c++20中,总修改顺序S
不考虑整个hb
)更改后,删除示例模式
sb;hb
。(见图3:S(k,l)
是sb
)(具体来说,是hb=sw
)。因此,旧的happens-beforeA,C
现在不在总修改顺序中考虑。