C++ 内存顺序消耗和内存顺序获取之间的差异

C++ 内存顺序消耗和内存顺序获取之间的差异,c++,c,atomic,memory-model,stdatomic,C++,C,Atomic,Memory Model,Stdatomic,我有一个关于一个问题的问题。标题“总体摘要”下给出了以下代码示例: 线程1: y.store (20); x.store (10); y.store (20); // Release; Evaluation A x.store (10); // Release; Evaluation B 线程2: if (x.load() == 10) { assert (y.load() == 20) y.store (10) } if

我有一个关于一个问题的问题。标题“总体摘要”下给出了以下代码示例:

线程1:

y.store (20);
x.store (10);
y.store (20);             //    Release; Evaluation A
x.store (10);             //    Release; Evaluation B
线程2:

if (x.load() == 10) {
  assert (y.load() == 20)
  y.store (10)
}
if (x.load() == 10) {     //    Consume; Evaluation C
  assert (y.load() == 20) //    Consume; Evaluation D
  y.store (10)
}
据说,如果所有存储都是release,所有加载都是acquire,线程2中的断言就不会失败。这对我来说很清楚(因为线程1中x的存储与线程2中x的加载同步)


但现在我不明白的部分来了。也有人说,如果所有存储都释放,而所有负载都消耗,则结果是相同的。难道y的荷载不可能在x的荷载之前提升(因为这些变量之间没有相关性)?这意味着线程2中的断言实际上可能会失败。

两者都在原子存储上建立了一个可传递的“可见性”顺序,除非它们是使用
内存顺序发布的。如果线程使用其中一种模式读取原子对象
x
,它可以确保看到在写入
x
之前对所有原子对象
y
所做的所有修改

“acquire”和“consume”之间的区别在于对某个变量
z
的非原子写入的可见性。对于
acquire
而言,所有写入操作,无论是否为原子写入,都是可见的。对于
消费
来说,只有原子的才保证可见

thread 1                               thread 2
z = 5 ... store(&x, 3, release) ...... load(&x, acquire) ... z == 5 // we know that z is written
z = 5 ... store(&x, 3, release) ...... load(&x, consume) ... z == ? // we may not have last value of z

C11标准的裁定如下

5.1.2.4多线程执行和数据争用
  • 如果出现以下情况,则评估A在评估B之前排序:

    -A对原子对象M执行释放操作,而在另一个线程中,B对M执行消耗操作,并读取由A开头的释放序列中的任何副作用写入的值,或

    -对于某些计算X,依赖项在X之前排序,X将依赖项带到B

  • 如果A与B同步,则A线程间的求值发生在求值B之前,A的依赖关系排序在B之前,或者,对于某些求值X:

    -A与X同步,X在B之前排序

    -A在X之前排序,X内部线程在B之前排序,或

    -一个内部线程发生在X之前,X个内部线程发生在B之前

  • 注7“线程间发生在”关系描述了“在”之前排序、“与”同步”和“依赖关系在”之前排序”关系的任意串联,但有两个例外。第一个例外是,串联不允许以“dependency-ordered before”结尾,后跟“sequenced before”。此限制的原因是,参与“dependency-ordered before”关系的消耗操作仅针对此消耗操作实际具有依赖关系的操作提供排序。此限制仅适用于此类串联的结尾的原因是后续发布操作将为先前的消费操作提供所需的订购。第二个例外是,串联不允许完全由“之前排序”组成。此限制的原因是(1)允许传递关闭“线程间发生在”之前;(2)下文定义的“发生在”关系提供了完全由“在”之前排序的关系组成的关系

  • 如果A在B之前排序,或者内部线程在B之前排序,则A在B之前排序

  • 关于M的值计算B,对象M上的可见副作用A满足以下条件:

    -A发生在B之前,并且

    -对于X到M没有其他副作用,比如A发生在X之前,X发生在B之前

    由评估B确定的非原子标量对象M的值应为可见副作用a存储的值

  • (增加重点)


    在下面的评论中,我将缩写如下:

    • 之前排序的依赖项:DOB
    • 线程间发生在以下时间之前:ITHB
    • 发生在:HB
    • 之前排序:SeqB

    让我们回顾一下这是如何应用的。我们有4个相关的内存操作,我们将其命名为评估A、B、C和D:

    线程1:

    y.store (20);
    x.store (10);
    
    y.store (20);             //    Release; Evaluation A
    x.store (10);             //    Release; Evaluation B
    
    线程2:

    if (x.load() == 10) {
      assert (y.load() == 20)
      y.store (10)
    }
    
    if (x.load() == 10) {     //    Consume; Evaluation C
      assert (y.load() == 20) //    Consume; Evaluation D
      y.store (10)
    }
    
    为了证明断言从未跳闸,我们实际上试图证明A在D处始终是可见的副作用。根据5.1.2.4(15),我们有:

    ASeqBBDOBCSeqBD

    这是一个以DOB结尾,后跟SeqB的串联。这是由(17)明确规定的是ITHB连接,不管(16)怎么说

    我们知道,由于A和D不在同一执行线程中,因此A不是seqbd;因此,(18)中关于HB的两个条件都不满足,A不满足HB D

    因此,A对D不可见,因为不满足(19)的条件之一。断言可能失败


    然后,描述了这将如何进行,以及:

  • (前面一段时间)线程2的分支预测器猜测将采用
    if
  • 线程2接近预测的take分支并开始推测性抓取
  • 线程2出现故障并推测性地从
    y
    加载
    0xGUNK
    (计算D)。(也许它还没有从缓存中移出?)
  • 线程1将
    20
    存储到
    y
    (评估A)
  • 线程1将
    10
    存储到
    x
    (评估B)
  • 螺纹2 lo