C++ C+中的确切规则是什么+;内存模型在获取操作之前是否阻止重新排序?

C++ C+中的确切规则是什么+;内存模型在获取操作之前是否阻止重新排序?,c++,language-lawyer,atomic,stdatomic,memory-barriers,C++,Language Lawyer,Atomic,Stdatomic,Memory Barriers,我对以下代码中的操作顺序有疑问: std::atomic<int> x; std::atomic<int> y; int r1; int r2; void thread1() { y.exchange(1, std::memory_order_acq_rel); r1 = x.load(std::memory_order_relaxed); } void thread2() { x.exchange(1, std::memory_order_acq_rel);

我对以下代码中的操作顺序有疑问:

std::atomic<int> x;
std::atomic<int> y;
int r1;
int r2;
void thread1() {
  y.exchange(1, std::memory_order_acq_rel);
  r1 = x.load(std::memory_order_relaxed);
}
void thread2() {
  x.exchange(1, std::memory_order_acq_rel);
  r2 = y.load(std::memory_order_relaxed);
}

现在,在同时执行
thread1
thread2
之后,是否可以使和
r1
r2
都等于0?如果不是,哪些C++规则阻止了?在原始版本中,

< P>,可以看到<代码> R1= 0和& R2==0 < /COD>,因为在读取之前,没有必要将存储转发到另一个线程。这不是对任一线程的操作进行重新排序,而是读取过时缓存

Thread 1's cache   |   Thread 2's cache
  x == 0;          |     x == 0;
  y == 0;          |     y == 0;

y.exchange(1, std::memory_order_acq_rel); // Thread 1
x.exchange(1, std::memory_order_acq_rel); // Thread 2
线程1上的释放被线程2忽略,反之亦然。在抽象机器中,线程上的
x
y
值不一致

Thread 1's cache   |   Thread 2's cache
  x == 0; // stale |     x == 1;
  y == 1;          |     y == 0; // stale

r1 = x.load(std::memory_order_relaxed); // Thread 1
r2 = y.load(std::memory_order_relaxed); // Thread 2
您需要更多的线程来获得acquire/release对的“违反因果关系”,因为正常的排序规则与“在中变得可见的副作用”规则相结合,强制至少一个
load
s查看
1

在不丧失一般性的情况下,我们假设线程1首先执行

Thread 1's cache   |   Thread 2's cache
  x == 0;          |     x == 0;
  y == 0;          |     y == 0;

y.exchange(1, std::memory_order_acq_rel); // Thread 1

Thread 1's cache   |   Thread 2's cache
  x == 0;          |     x == 0;
  y == 1;          |     y == 1; // sync 
线程1上的释放与线程2上的获取形成一对,抽象机器在两个线程上描述了一致的
y

r1 = x.load(std::memory_order_relaxed); // Thread 1
x.exchange(1, std::memory_order_acq_rel); // Thread 2
r2 = y.load(std::memory_order_relaxed); // Thread 2

<>标准不定义C++内存模型,具体是如何在具有特定排序参数的原子操作周围排序。 相反,对于acquire/release排序模型,它定义了形式化关系,例如“synchronizes with”和“before”,用于指定如何在线程之间同步数据

N4762,§29.4.2-[原子秩序]

对原子对象M执行释放操作的原子操作A与对M执行获取操作的原子操作B同步 并从以A为首的释放序列中的任何副作用中获取其值

在§6.8.2.1-9中,该标准还规定,如果存储a与负载B同步,则在线程间之前排序的任何内容“发生在”之前,在B之后排序的任何内容

在您的第二个示例中没有建立“与同步”(因此线程间发生在之前)关系(第一个更弱),因为缺少运行时关系(检查加载返回值)。
但是,即使您确实检查了返回值,它也不会有帮助,因为
交换操作实际上不会“释放”任何内容(即,在这些操作之前没有对内存操作进行排序)。
不要让原子加载操作“获取”任何东西,因为加载后没有操作排序

因此,根据标准,两个示例中荷载的四种可能结果(包括0)均有效。 事实上,该标准给出的保证在所有操作上都不强于
内存\u顺序

如果要在代码中排除0结果,则所有4个操作都必须使用
std::memory\u order\u seq\u cst
。这保证了所涉及操作的总顺序。

为了在两个线程之间创建同步点,我们需要一些原子对象
M
,在两个操作中都是相同的

一种原子操作,它对一个 原子对象
M
与原子操作同步
B
M
执行获取操作并从任何 以
A
为首的发布序列中的副作用

或更详细地说:

如果线程
A
中的原子存储被标记为
memory\u order\u release
来自同一变量的线程
B
中的原子负载被标记
内存\u顺序\u获取
,所有内存写入(非原子和松弛) 原子的)在原子存储之前发生的 线程
A
,在线程
B
中会出现明显的副作用。那个 即,一旦原子加载完成,线程
B
将保证 查看线程写入内存的所有内容

同步仅在线程之间建立 以及获取相同的原子变量

这里的同步点位于
M
存储释放和加载获取(从存储释放中获取值!)。因此,在线程
中存储
N=u
(在
M
上的存储释放之前)在
B
中可见(
N==u
)在相同
M
上加载后

例如:

atomic<int> x, y;
int r1, r2;

void thread_A() {
  y.exchange(1, memory_order_acq_rel);
  r1 = x.load(memory_order_acquire);
}
void thread_B() {
  x.exchange(1, memory_order_acq_rel);
  r2 = y.load(memory_order_acquire);
}
假设
r1==0

对任何特定原子变量的所有修改都是以总计的形式进行的 特定于此原子变量的顺序

我们对
y
进行了两次修改:
[Ay]
[By]
。因为
r1==0
这意味着
[Ay]
发生在
[By]
之前,总修改顺序为
y
。从此-
[By]
读取
[Ay]
存储的值。因此,我们有下一步:

  • A
    写入
    x
    -
    [Ax]
  • A
    do store release
    [Ay]
    y
    在此之后(acq\u rel包括release, 交换(包括存储)
  • B
    y
    [By]
    存储的值
    [Ay]
  • 原子负载获取(在
    y
    上)完成后,线程
    B
    将被激活 保证看到线程
    A
    之前写入内存的所有内容 商店放行(
         N = u                |  if (M.load(acquire) == v)    :[B]
    [A]: M.store(v, release)  |  assert(N == u)
    
    atomic<int> x, y;
    int r1, r2;
    
    void thread_A() {
      y.exchange(1, memory_order_acq_rel);
      r1 = x.load(memory_order_acquire);
    }
    void thread_B() {
      x.exchange(1, memory_order_acq_rel);
      r2 = y.load(memory_order_acquire);
    }
    
    atomic<int> x, y;
    int r1, r2;
    
    void thread_A()
    {
        x.exchange(1, memory_order_acq_rel); // [Ax]
        r1 = y.exchange(1, memory_order_acq_rel); // [Ay]
    }
    
    void thread_B()
    {
        y.exchange(1, memory_order_acq_rel); // [By]
        r2 = x.exchange(1, memory_order_acq_rel); // [Bx]
    }
    
    atomic<int> x, y;
    int r1, r2;
    
    void thread_A()
    {
        x.store(1, memory_order_relaxed); // [A1]
        atomic_thread_fence(memory_order_acq_rel); // [A2]
        r1 = y.exchange(1, memory_order_relaxed); // [A3]
    }
    
    void thread_B()
    {
        y.store(1, memory_order_relaxed); // [B1]
        atomic_thread_fence(memory_order_acq_rel); // [B2]
        r2 = x.exchange(1, memory_order_relaxed); // [B3]
    }
    
    [A1]: x = 1               |  if (y.load(relaxed) == 1) :[B1]
    [A2]: ### release ###     |  ### acquire ###           :[B2]
    [A3]: y.store(1, relaxed) |  assert(x == 1)            :[B3]
    
    foo:
        lwsync            # with seq_cst exchange this is full sync, not just lwsync
                          # gone if we use exchage with mo_acquire or relaxed
                          # so this barrier is providing release-store ordering
        li %r9,1
    .L2:
        lwarx %r10,0,%r4    # load-linked from 0(%r4)
        stwcx. %r9,0,%r4    # store-conditional 0(%r4)
        bne %cr0,.L2        # retry if SC failed
        isync             # missing if we use exchange(1, mo_release) or relaxed
    
        ld %r3,0(%r3)       # 64-bit load double-word of *a
        cmpw %cr7,%r3,%r3
        bne- %cr7,$+4       # skip over the isync if something about the load? PowerPC is weird
        isync             # make the *a load a load-acquire
        blr
    
    thread1():
        adrp    x0, .LANCHOR0
        mov     w1, 1
        add     x0, x0, :lo12:.LANCHOR0
    .L2:
        ldaxr   w2, [x0]            @ load-linked with acquire semantics
        stlxr   w3, w1, [x0]        @ store-conditional with sc-release semantics
        cbnz    w3, .L2             @ retry until exchange succeeds
    
        add     x1, x0, 8           @ the compiler noticed the variables were next to each other
        ldar    w1, [x1]            @ load-acquire
    
        str     w1, [x0, 12]        @ r1 = load result
        ret
    
    r1 = x.load(std::memory_order_acquire);