【发布时间】:2017-03-15 00:08:31
【问题描述】:
众所周知,std::atomic 和 volatile 是不同的东西。
有两个主要区别:
-
两个优化可以针对
std::atomic<int> a;,但不能针对volatile int a;:- 融合操作:
a = 1; a = 2;可以被a = 2;上的编译器替换 - 不断传播:
a = 1; local = a;可以被编译器替换为a = 1; local = 1;
- 融合操作:
-
跨原子/易失性操作的普通读/写重新排序:
- 对于
volatile int a;,任何易失性读/写操作都不能重新排序。但是附近的普通读/写仍然可以围绕易失性读/写重新排序。 - 用于
std::atomic a;附近普通读/写的重新排序,基于用于原子操作的内存屏障a.load(std::memory_order_...);
- 对于
即volatile 不引入内存围栏,但 std::atomic 可以做到。
正如文章中所描述的那样:
- Herb Sutter,2009 年 1 月 8 日 - 第 1 部分:http://www.drdobbs.com/parallel/volatile-vs-volatile/212701484
- Herb Sutter,2009 年 1 月 8 日 - 第 2 部分:http://www.drdobbs.com/parallel/volatile-vs-volatile/212701484?pgno=2
例如std::atomic应该用于并发多线程程序(CPU-Core CPU-Core),但volatile应该用于访问设备上的Mamory Mapped Regions(CPU-Core 设备)。
但如果需要,两者都具有不寻常的语义,并具有无锁编码所需的任何或全部原子性和/或顺序保证,即如果需要 volatile std::atomic<>,出于以下几个原因:
- 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;
- 溢出:CPU 写入 CPU-RAM,然后设备 DMA 控制器从该内存读取:https://en.wikipedia.org/wiki/Register_allocation#Spilling
示例:
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<T>,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<T> 具有两种语义(易失性 + 原子) ?
volatile atomic<T> 是否保证 volatile 和 atomic 的最严格保证?
- 如
volatile:避免问题开头所述的融合操作和常量传播 - 如
std::atomic:引入内存栅栏以提供排序、溢出和原子性。
我们可以从volatile int *ptr; 到volatile std::atomic<int>* 做reinterpret_cast 吗?
【问题讨论】:
-
让我做一个简短的评论。
volatile atomic<T>超过atomic<volatile T>,你为什么要做reinterpret_cast?它可能会起作用,但不能保证。 -
您不能拥有
std::atomic<volatile T>,因为 volatile 类型不可轻易复制。 -
@Brian 是的,你是对的。删除了大约
std::atomic<volatile T>。 -
@DeiDei 如果驱动程序 API 返回
volatile int *ptr;并且我想使用代码while(ptr->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