【问题标题】:Does standard C++11 guarantee that `volatile atomic<T>` has both semantics (volatile + atomic)?标准 C++11 是否保证 `volatile atomic<T>` 具有两种语义(volatile + atomic)?
【发布时间】:2017-03-15 00:08:31
【问题描述】:

众所周知,std::atomicvolatile 是不同的东西。

有两个主要区别:

  1. 两个优化可以针对std::atomic&lt;int&gt; a;,但不能针对volatile int a;

    • 融合操作:a = 1; a = 2; 可以被a = 2; 上的编译器替换
    • 不断传播:a = 1; local = a;可以被编译器替换为a = 1; local = 1;
  2. 跨原子/易失性操作的普通读/写重新排序:

    • 对于volatile int a;,任何易失性读/写操作都不能重新排序。但是附近的普通读/写仍然可以围绕易失性读/写重新排序。
    • 用于std::atomic a; 附近普通读/写的重新排序,基于用于原子操作的内存屏障a.load(std::memory_order_...);

volatile 不引入内存围栏,但 std::atomic 可以做到。

正如文章中所描述的那样:

例如std::atomic应该用于并发多线程程序(CPU-Core CPU-Core),但volatile应该用于访问设备上的Mamory Mapped Regions(CPU-Core 设备)。


但如果需要,两者都具有不寻常的语义,并具有无锁编码所需的任何或全部原子性和/或顺序保证,即如果需要 volatile std::atomic&lt;&gt;,出于以下几个原因:

  • ordering:防止对普通读取/写入进行重新排序,例如,从 CPU-RAM 读取数据,使用设备 DMA 控制器将数据写入其中

例如:

char cpu_ram_data_written_by_device[1024];
device_dma_will_write_here( cpu_ram_data_written_by_device );

// physically mapped to device register
volatile bool *device_ready = get_pointer_device_ready_flag();

//... somewhere much later
while(!device_ready); // spin-lock (here should be memory fence!!!)
for(auto &i : cpu_ram_data_written_by_device) std::cout << i;

示例:

char cpu_ram_data_will_read_by_device[1024];
device_dma_will_read_it( cpu_ram_data_written_by_device );

// physically mapped to device register
volatile bool *data_ready = get_pointer_data_ready_flag();

//... somewhere much later
for(auto &i : cpu_ram_data_will_read_by_device) i = 10;
data_ready=true; //spilling cpu_ram_data_will_read_by_device to RAM, should be memory fence
  • atomic:保证 volatile 操作是原子操作 - 即它将由单个操作而不是多个操作组成 - 即一个 8 字节操作而不是两个 4 字节操作

为此,Herb Sutter 谈到了 volatile atomic&lt;T&gt;,2009 年 1 月 8 日:http://www.drdobbs.com/parallel/volatile-vs-volatile/212701484?pgno=2

最后,表达一个既具有不寻常语义又具有 所需的任何或所有原子性和/或排序保证 无锁编码,只有 ISO C++0x 草案标准提供了直接 拼写方式:volatile atomic。

但是现代标准 C++11(不是 C++0x 草案)、C++14 和 C++17 是否保证 volatile atomic&lt;T&gt; 具有两种语义(易失性 + 原子) ?

volatile atomic&lt;T&gt; 是否保证 volatile 和 atomic 的最严格保证?

  1. volatile:避免问题开头所述的融合操作和常量传播
  2. std::atomic:引入内存栅栏以提供排序、溢出和原子性。

我们可以从volatile int *ptr;volatile std::atomic&lt;int&gt;*reinterpret_cast 吗?

【问题讨论】:

  • 让我做一个简短的评论。 volatile atomic&lt;T&gt; 超过 atomic&lt;volatile T&gt;,你为什么要做 reinterpret_cast?它可能会起作用,但不能保证。
  • 您不能拥有std::atomic&lt;volatile T&gt;,因为 volatile 类型不可轻易复制。
  • @Brian 是的,你是对的。删除了大约std::atomic&lt;volatile T&gt;
  • @DeiDei 如果驱动程序 API 返回 volatile int *ptr; 并且我想使用代码 while(ptr-&gt;load(std::memory_order_acquire) == 0); 而不是 while(*ptr == 0); std::atomic_thread_fence(std::memory_order_acquire);
  • " 一个 8 字节操作而不是两个 4 字节操作" - atomic 不保证这一点。它很可能需要一个锁,然后执行两次 4 字节写入。 ATOMIC_LONG_LOCK_FREE 可以是 0 说“永不无锁”。

标签: c++ multithreading c++11 concurrency volatile


【解决方案1】:

是的,确实如此。

第 29.6.5 节,“原子类型操作的要求”

许多操作都是 volatile 限定的。 “作为设备寄存器的易失性”语义在标准中没有改变。这种限定意味着在将这些操作应用于 volatile 对象时会保留波动性。

我检查了 2008 年到 2016 年的工作草案,所有这些草案中的文本都是相同的。因此它应该应用 C++11、C++14 和 C++17。

【讨论】:

    【解决方案2】:

    我们可以从volatile int *ptr;volatile std::atomic<int>* 进行reinterpret_cast 吗?

    当且仅当 ABI 表明两种类型(此处为 intstd::atomic&lt;int&gt;)具有相同的表示和限制:相同的大小、对齐方式和可能的位模式;相同的位模式具有相同的含义。

    所有 volatile 都与 ABI 直接相关:具有 volatile 限定的变量必须在序列点处具有规范的 ABI 表示,并且对 volatile 对象的操作仅假设它们遵循其 ABI 要求,仅此而已。因此,无论何时在 C 或 C++ 中使用 volatile,您都可以选择依赖语言标准或平台 ABI。

    (我希望这个答案不会被删除,因为有些人鄙视 volatile 语义并且取决于 ABI 和平台特定的概念。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-18
      • 2021-06-21
      • 2015-12-28
      • 1970-01-01
      • 2013-09-25
      • 2011-10-01
      • 2015-05-24
      • 1970-01-01
      相关资源
      最近更新 更多