C++ C++;20标准::原子<;浮动>;-标准::原子<;双倍>;。专业

C++ C++;20标准::原子<;浮动>;-标准::原子<;双倍>;。专业,c++,multithreading,floating-point,memory-model,stdatomic,C++,Multithreading,Floating Point,Memory Model,Stdatomic,C++20包括原子和原子的专门化。这里有人能解释一下这有什么实际用途吗?我能想象的唯一目的是当我有一个线程在随机点异步更改原子双精度或浮点值,而其他线程异步读取该值时(但在大多数平台上,易失性双精度或浮点值实际上也应该这样做)。但这种需要应该是极其罕见的。我认为这种罕见的情况不能证明将其纳入C++20标准是合理的。编辑:添加乌尔里希·埃克哈特的评论以澄清: 让我试着重新表述一下:即使volatile在一个特定的平台/环境/编译器上做了与atomic相同的事情,一直到生成的机器代码,那么atomi

C++20包括
原子
原子
的专门化。这里有人能解释一下这有什么实际用途吗?我能想象的唯一目的是当我有一个线程在随机点异步更改原子双精度或浮点值,而其他线程异步读取该值时(但在大多数平台上,易失性双精度或浮点值实际上也应该这样做)。但这种需要应该是极其罕见的。我认为这种罕见的情况不能证明将其纳入C++20标准是合理的。

编辑:添加乌尔里希·埃克哈特的评论以澄清:
让我试着重新表述一下:即使volatile在一个特定的平台/环境/编译器上做了与atomic相同的事情,一直到生成的机器代码,那么atomic在保证方面仍然更具表现力,而且,它保证是可移植的。此外,当您可以编写自文档代码时,您应该这样做。”

Volatile有时具有以下两种效果:

  • 防止编译器缓存寄存器中的值
  • 防止在程序的POV看来不必要时优化对该值的访问
  • 另见

    TLDR

    明确你想要什么

    • 不要依赖于“volatile”,如果“what”不是volatile的最初目的,请做您想做的事情,例如启用外部传感器或DMA更改内存地址而不受编译器干扰
    • 如果需要原子,请使用std::atomic
    • 如果要禁用严格别名优化,请像Linux内核一样,在gcc上禁用严格别名优化
    • 如果要禁用其他类型的编译器优化,请对ARM或x86_64等使用编译器内部函数或代码显式汇编
    • 如果你想在C中“限制”关键字语义,那么在编译器上使用相应的C++内部约束,如果可用的话。
    • 简而言之,如果标准提供的构造更清晰、更可移植,则不要依赖于编译器和CPU系列相关的行为。如果你认为你的“黑客”比正确的方法更有效,可以使用godbolt.org来比较汇编程序的输出

    与挥发性有机物的关系 在一个执行线程内,通过volatile glvalues的访问(读写)不能重新排序,而不能超过在同一线程内之前或之后排序的可观察副作用(包括其他volatile访问),但不能保证另一个线程会遵守此顺序,因为volatile访问不建立线程间同步

    此外,易失性访问不是原子性的(并发读写是一种数据竞争),并且不排序内存(非易失性内存访问可以围绕易失性访问自由重新排序)

    一个值得注意的例外是VisualStudio,在默认设置下,每个volatile写入都有发布语义,每个volatile读取都有获取语义(MSDN),因此volatile可以用于线程间同步。标准volatile语义不适用于多线程编程,尽管它们足以与应用于sig_原子变量时在同一线程中运行的std::signal处理程序进行通信

    作为最后的一个咆哮:在实践中,构建OS内核的唯一可行语言通常是C和C++。考虑到这一点,我希望2个标准中关于“告诉编译器退出”的规定,即能够明确地告诉编译器不要更改代码的“意图”。其目的是使用C或C++作为一个可移植的汇编程序,甚至比今天更大。 值得在godbolt.org上为ARM和x86_64(均为gcc)编译一个有点愚蠢的代码示例,以了解在ARM情况下,编译器为原子生成两个u sync_synchronize(HW CPU屏障)操作,但不为代码的易失性变体生成(取消您想要的注释)。要点是使用原子可以提供可预测的、可移植的行为

    #include <inttypes.h>
    #include <atomic>
    
    std::atomic<uint32_t> sensorval;
    //volatile uint32_t sensorval;
    
    uint32_t foo()
    {
        uint32_t retval = sensorval;
        return retval;
    }
    int main()
    {
        return (int)foo();
    }
    
    对于那些想要X86示例的人,我的同事Angus Lepper慷慨地提供了以下示例:

    我能想象的唯一目的是当我有一个线程改变 在随机点和其他点上异步双原子或浮点 线程异步读取这些值

    是的,这是原子的唯一用途,不管实际类型如何。它可能是原子的
    bool
    char
    int
    long
    或其他任何东西

    无论您对
    type
    有什么用法,
    std::atomic
    都是它的线程安全版本。 无论您对
    float
    double
    有什么用法,
    std::atomic
    都可以以线程安全的方式进行写入、读取或比较

    std::atomic
    只有很少的用法实际上是说
    float/double
    有很少的用法。

    原子的
    原子的
    从C++11开始就存在了。
    atomic
    模板适用于任意可复制的
    T
    使用旧的C++11之前版本可以破解的所有东西使用
    volatile
    共享变量可以使用C++11
    atomic
    std::memory\u order\u relaxed
    完成

    在C++20之前不存在的是原子RMW操作,如
    x.fetch\u add(3.14)或简称
    x+=3.14
    。(不知道为什么不)。这些成员函数仅在
    原子
    整数专门化中可用,因此您只能在
    浮点
    双精度
    上加载、存储、交换和CAS,类似于任意
    T
    类类型

    有关如何使用
    compare\u exchange\u weak
    滚动您自己的,以及如何使用(以及纯加载、纯s)的详细信息,请参阅
    foo():
      push {r4, lr}
      ldr r4, .L4
      bl __sync_synchronize
      ldr r4, [r4]
      bl __sync_synchronize
      mov r0, r4
      pop {r4, lr}
      bx lr
    .L4:
      .word .LANCHOR0
    
    #include <atomic>
    
    volatile double vx;
    std::atomic<double> ax;
    double px; // plain x
    
    void FP_non_RMW_increment() {
        px += 1.0;
        vx += 1.0;     // equivalent to vx = vx + 1.0
        ax.store( ax.load(std::memory_order_relaxed) + 1.0, std::memory_order_relaxed);
    }
    
    #if __cplusplus > 201703L    // is there a number for C++2a yet?
    // C++20 only, not yet supported by libstdc++ or libc++
    void atomic_RMW_increment() {
        ax += 1.0;           // seq_cst
        ax.fetch_add(1.0, std::memory_order_relaxed);   
    }
    #endif
    
    FP_non_RMW_increment():
            movsd   xmm0, QWORD PTR .LC0[rip]   # xmm0 = double 1.0 
    
            movsd   xmm1, QWORD PTR px[rip]        # load
            addsd   xmm1, xmm0                     # plain x += 1.0
            movsd   QWORD PTR px[rip], xmm1        # store
    
            movsd   xmm1, QWORD PTR vx[rip]
            addsd   xmm1, xmm0                     # volatile x += 1.0
            movsd   QWORD PTR vx[rip], xmm1
    
            mov     rax, QWORD PTR ax[rip]      # integer load
            movq    xmm2, rax                   # copy to FP register
            addsd   xmm0, xmm2                     # atomic x += 1.0
            movq    rax, xmm0                   # copy back to integer
            mov     QWORD PTR ax[rip], rax      # store
    
            ret
    
    <source>: In function 'void FP_non_RMW_increment()':
    <source>:9:8: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
        9 |     vx += 1.0;     // equivalent to vx = vx + 1.0
          |     ~~~^~~~~~