c++ 如何理解Sequentially-consistent和happen-before?

oknrviil  于 2023-08-09  发布在  其他
关注(0)|答案(1)|浏览(66)
// 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出现呢?

gcuhipw9

gcuhipw91#

以下内容使用了多层次的降价商品列表,在移动的设备上可能难以查看。
最近我也读了同样的cppreference部分,也有和你一样的问题,在看了相关的SO QA和论文后,我有了这个想法。我希望这个答案也能帮助你理解cppreference是怎么说的。
您的问题似乎是QA_1QA_2的 * 副本 ,但两个QA似乎都没有读取cppreference_1和paper_2中引用的原始paper_1。所以在这里,我将主要根据论文_1,部分根据其参考文献paper_3,从数学的Angular 给予一个答案,如果有数学基础知识,可能有助于把握其内在思想。
这些都是基于我对论文的理解和相关链接。
如有错误,请指出 *,谢谢。

简答:

1.查看QA_1问题中的引用,您将获得基于旧c++11标准的上述冲突发生的场合。
QA_1回答说这在真实的世界中是如何发生的(可能是由于该高速缓存一致性导致不同线程看到不同顺序的不同变量)。

  • 更具体地说,请查看图3及其上下文、(S1 fix)上下文和paper_1中的相关术语定义
  • “A happens-before C”对应于S(k, m)
  • A排序在(sb)B之前(这是因为它们通过cppreference_2位于同一个线程中)

B(sw)C关于变量y而不是关于x同步,因此A发生在C之前。(这是“发生在”之前的定义:由sb和sw组成的序列)

  • 总的修改顺序“C-E-F-A”对应于S(m, o, p, k)(这里我按顺序合并了不同的S(,))。
  • 这里S(p,k)是由于“reads-before”(IMO,这意味着“reads-before-write”是由于x1c 0d1x。这是为了解决战争的危险)。
  • 然后上面导致了循环,所以在paper_1中它说“What Went Wrong and How to Fix it”上下文,并且它在(S1 fix)中重新定义了。正如paper_2所说,c++20标准现在基于它。
  • 在这里,你可以认为它分裂了原始周期(即旧的c++11总修改顺序)分为两部分S(k, m)S(m, o, p, k),其中前者保持“happens-before”,后者是新的总修改顺序。
  • 更具体地说,原始问题可以有一个观察顺序(A)-B-C-E-D-F-A(这里的“(A)”表示它在之前运行,但在之后观察到)。
  • 这里A不与C同步,因此A不一定需要被C观察。

注意:* 也许 * 上面在c++20中引入的优化内存模型仍然有缺陷。然而,我不是一个编译器/计算机体系结构Maven,所以找到缺陷超出了我的能力。

详细解答主要基于论文_1:

下面需要一些离散数学的知识,我为暂时不熟悉的人添加一些描述。

如果你不想被“数学”卡住,那么查看下面的“非数学”部分就足够了。

注意:这里只对一些符号进行了重新表述,如果对某些术语有疑问,最好查看原文

  • 数学

下面的部分原始符号定义(主要是关于关系的)可以从paper_1中的“Notation 1”和paper_1中引用的like (;) paper_3中的“Definition 8”中看到。

  • 在这里,我假设他们在论文中使用相同的数学原始符号,因为paper_1“备注1”说:

我们在这里使用Batty等人的版本的原因是它为我们的讨论提供了一个更清晰的起点,我们对C11的SC语义问题的解决方案将建立在它的基础上。
在查看p5中的paper_3脚注后,它主要基于ISO标准,并将其Map到 * 纯数学 *,如果有更好的数学知识,可能会更直观。


x的意思。)

这里;是关系的组合(即

表示一个流水线,如[A] -> R -> [B])。
这意味着一个AB对具有关系R。

  • happens-before定义:
  • 非数学
  • 请参阅以上2个QA中引用的内容(由于有2个级别,因此优于cppreference)。

A在B之前是依赖性排序的,或者
这意味着包括consume

  • 正如paper_1所说:

除了新的SC和NO -THIN - AIR条件外,RC 11与C11在其他几个方面有所不同。不支持消耗访问
因此,上面的cppreference定义可能比paper_1中的以下数学定义更通用。

  • 数学



接下来,我们说一个事件发生在另一个事件之前(hb),如果它们由**sbsw边序列连接**

  • +表示可传递(即sequence),U表示“或”。

由于S(l,m)同步,上述S(k,m)具有“happens-before”关系(这是由于l,m具有隐含的数据依赖性(即,我在读我写的

  • sw的定义
  • 非数学

如cppreference_1的其他部分所述,将其视为relacq序列。
如果线程A中的原子存储是release操作,线程B中的原子load相同变量是acquire操作,线程B中的load读取线程A中的存储写入的值,则线程A中的存储与线程B中的加载同步。

  • 数学(更多详情请参见paper_1)

x1c4d 1x型
接下来,释放事件a与获取事件B同步(sw),每当b(,在b是栅栏的情况下,一些sb-prior读取)从a(或在a是栅栏的情况下,一些sb-later写入)的释放序列读取时。

  • 由上可知,反身符号?意味着“或”关系。
  • 其中

    表示偏序,所以

    包含了论文_1第7页右下图中的sc
  • 并且[F]总是显式地放置在weak架构中,如paper_1中引用的POWERPC,它与relacq等相关(有关编译器如何添加这些围栏,请参阅paper_2中的link)。
  • rs; rfrelacq表示


是考虑RAR(读后读)。

  • rs的定义

rs的定义中,rf; rmw是为了得到类似“写,读,写”的序列。

*综上所述,以上表示rel_event,(fence-sync),release_relation,read-from,relaxed_read,(fence-sync),acq_event

  • 这里“relaxed_read”存在,因为它不影响relacq之间的关系。
  • sb的定义。在这里,我采用paper_3中的定义来强调它们在同一线程中。
  • 非数学

来自cppreference_2:
Sequenced before是同一线程内求值之间的非对称传递、成对关系。

  • 数学



sb表示:
来自同一线程的事件之间的严格偏序(即非自反和传递),它捕获程序顺序。

thd

    • 非初始**事件上的等价关系,它将来自同一线程的事件关联起来。

I
一组初始事件。每个初始事件

是一个非原子写零;即kind(e)= Wna和wval(e)= 0。此外,每个位置正好有一个初始事件。

  • 单次全阶的定义(论文中的S_1)
  • 非数学

正如paper_1所说:
在所有SC事件上应该有严格的顺序S,直观地对应于这些事件被执行的顺序。

  • 数学论文_3

S关系以严格总计顺序,所有且仅执行中的SC事件相关;即,


  • 这里id是“恒等关系”


意味着S命令是运行时定义的。

  • acy(S)(S是非循环的)是重要的,因为这导致了上面的示例失败。
  • “单总序”与“先发生”的关系及其变化原因
  • C++11中的原始版本,来自paper_1:

(S1)S必须包括仅限于SC事件的hb
(formally:

);
[...](这些与论文未发现的问题保持一致)
非数学:S需要符合hb(happens-before)顺序
数学:见上面的等式。

  • 这是如何产生问题的->参见上面的“A发生在C之前”。
  • 为什么原始的C++11模型失败了?

因为它drops of sync fences.然后它暗示了weaker内存模型。

  • 引用paper_1“修复模型”:

相反,如果我们遵循前导同步约定,则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可见。

  • 注意:这里的“this outcome”是循环的结果(原始语言规范预期不允许,但在机器上发生)
  • 这里说POWER没有关于内存围栏的传递性,这个depends在架构上。

(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边缘开始和结束,要么以访问相同位置开始和结束

  • 上面的“either”情况加上隐含的同线程sb(因为这不会导致程序顺序的循环)现在在c++20中被允许,而不是整个hb(happens-before)。
  • “相同位置”由同步暗示。
  • 数学

(形式上:

,其中

表示对相同位置的访问之间的HB边)。
hb-> sb...(即现在在c++20中,总修改顺序S不考虑整个hb

  • 更改如何工作:

更改后,删除示例模式sb;hb。(见图3:S(k,l)sb)(具体来说,是hb=sw)。因此,旧的happens-before A,C现在不在总修改顺序中考虑

  • 还有其他的例子也工作后的变化,尝试他们,如果你有兴趣。

相关问题