C++ C++;11:放松的记忆顺序和消耗的记忆顺序之间的区别

C++ C++;11:放松的记忆顺序和消耗的记忆顺序之间的区别,c++,c++11,memory-model,stdatomic,C++,C++11,Memory Model,Stdatomic,我现在正在学习并想了解内存顺序和内存顺序之间的区别 具体地说,我正在寻找一个简单的例子,在这个例子中,人们不能用内存顺序替换内存顺序 有一个很好的例子,详细介绍了一个简单但非常说明性的例子,其中可以应用内存\u顺序\u消耗。下面是文字复制粘贴 例如: 原子保护(nullptr); int有效载荷=0; 制作人: Payload=42; 保护、存储(有效负载、内存、命令和释放); 消费者: g=Guard.load(内存\u顺序\u消耗); 如果(g!=nullptr) p=*g; 我的问

我现在正在学习并想了解
内存顺序
内存顺序
之间的区别

具体地说,我正在寻找一个简单的例子,在这个例子中,人们不能用
内存顺序
替换
内存顺序

有一个很好的例子,详细介绍了一个简单但非常说明性的例子,其中可以应用
内存\u顺序\u消耗
。下面是文字复制粘贴

例如:

原子保护(nullptr);
int有效载荷=0;
制作人:

Payload=42;
保护、存储(有效负载、内存、命令和释放);
消费者:

g=Guard.load(内存\u顺序\u消耗);
如果(g!=nullptr)
p=*g;
我的问题包括两部分:

  • 在上面的例子中,是否可以用
    内存\u顺序\u松弛的
    替换
    内存\u顺序\u消耗的
  • 有人能举一个类似的例子吗?
    memory\u order\u consume
    不能替换为
    memory\u order\u released
  • 问题1 否。
    memory\u order\u released
    完全不强制执行内存顺序:

    轻松操作:没有同步或排序约束,此操作只需要原子性

    memory\u order\u consume
    对依赖于数据的读取(在当前线程上)施加内存顺序

    具有此内存顺序的加载操作对受影响的内存位置执行消耗操作:在此加载之前,当前线程中依赖于当前加载值的任何读取都不能重新排序

    编辑

    通常
    memory\u order\u seq\u cst
    更强
    memory\u order\u acq\u rel
    更强
    memory\u ordering\u rel

    这就像有一个电梯a可以提升800公斤,电梯C可以提升100公斤。
    现在,如果你有能力神奇地把电梯A换成电梯C,那么如果电梯A里有10个平均体重的人,会发生什么? 那太糟糕了

    <>看看代码到底出了什么问题,考虑一下你的问题的例子:

    Thread A                                   Thread B
    Payload = 42;                              g = Guard.load(memory_order_consume);
    Guard.store(1, memory_order_release);      if (g != 0)
                                                   p = Payload;
    
    这段代码旨在循环,两个线程之间没有同步,只有顺序

    随着
    内存\u顺序\u的放松
    ,并假设自然单词加载/存储是原子的,那么代码将相当于

    Thread A                                   Thread B
    Payload = 42;                              g = Guard
    Guard = 1                                  if (g != 0)
                                                   p = Payload;
    
    从CPU的角度来看,线程a上有两个存储到两个单独的地址,因此如果
    Guard
    更接近另一个处理器的CPU(意味着存储完成得更快),那么线程a似乎正在运行

    Thread A
    Guard = 1
    Payload = 42
    
    这种执行顺序是可能的

    Thread A   Guard = 1
    Thread B   g = Guard
    Thread B   if (g != nullptr) p = Payload
    Thread A   Payload = 42
    
    这很糟糕,因为线程B读取了一个未更新的有效负载值

    然而,在线程B中,同步似乎是无用的,因为CPU不会执行类似的重新排序

    Thread B
    if (g != 0) p = Payload;
    g = Guard
    
    但事实上会的

    从它的角度来看,有两个不相关的负载,一个是在依赖的数据路径上,但CPU仍然可以推测地执行负载:

    Thread B
    hidden_tmp = Payload;
    g = Guard
    if (g != 0) p = hidden_tmp
    
    这可能会生成序列

    Thread B   hidden_tmp = Payload;
    Thread A   Payload = 42;
    Thread A   Guard = 1;
    Thread B   g = Guard
    Thread B   if (g != 0) p = hidden_tmp
    
    哎呀

    问题2 一般来说,这是不可能做到的。
    当您要在加载的值和需要对其进行访问排序的值之间生成地址依赖关系时,可以将
    内存\u顺序\u获取
    替换为
    内存\u顺序\u消耗


    要理解
    memory\u order\u relaxed
    我们可以参考ARM架构。
    ARM体系结构只要求弱内存顺序,这意味着通常程序的加载和存储可以按任何顺序执行

    str r0, [r2]
    str r0, [r3]
    
    在上面的代码段中,可以在外部观察到存储到
    [r3]
    之前的存储到
    [r2]
    1

    但是,CPU没有Alpha CPU那么远,并且强制执行:当使用来自内存的值加载来计算另一个加载/存储的地址时,执行地址依赖关系;以及当使用来自内存的值加载来计算另一个加载/存储的控制标志时,执行控制依赖关系

    在存在这种依赖性的情况下,两个内存操作的顺序保证为:

    如果存在地址依赖关系,则按程序顺序观察两次内存访问

    因此,虽然
    内存\u顺序\u获取
    会产生内存障碍,但是
    内存\u顺序\u消耗
    告诉编译器,使用加载值的方式将产生地址依赖性,因此如果与体系结构相关,它可以利用这一事实并忽略内存障碍


    1如果
    r2
    是同步对象的地址,那就不好了

    在上面的例子中,是否可以用
    内存\u顺序\u松弛的
    替换
    内存\u顺序\u消耗的

    在ISO C++中安全:否。

    在大多数ISA的大多数实施中,通常是肯定的。它通常编译为asm,在第一次加载结果和第二次加载的地址之间具有数据依赖关系,大多数ISA都保证这种顺序。(这是硬件功能
    consume
    打算公开的内容)

    但是,由于C++11设计的
    消费
    对于编译器来说是不切实际的,所以他们都只是放弃并加强它以
    获取
    ,在大多数弱有序的ISA上需要一个内存屏障。(例如电源或ARM,但不是x86)

    因此,在现实生活中,为了获得阅读几乎永远不会改变的东西的有趣性能,一些真正的代码(如RCU)确实谨慎地使用了
    released
    ,我们希望不会以不安全的方式进行优化。参见Paul E.McKenney的CppCon 2016演讲:关于Linux如何使用它使RCU端的读取非常便宜,没有障碍。(在内核中,他们只是使用
    volatile
        if (g == expected_address)
            p = *expected_address;
        else
            p = *g;