Volatile关键字的作用
- 保证可见性
volatile关键字可以保证变量在不同线程内的可见性,即在一个线程中修改了某变量后,另一个线程一定能读取到被修改的值。
2.防止指令重排
在代码编译阶段,C++编译器会对已有的代码进行优化,优化过程中就可能伴随着指令重排,例如
int a = 1;
int b = 2;
a = a + 1;
b = b + 1;
这段代码在经过指令重排后会变为
int a = 1;
a = a + 1;
int b = 2;
b = b + 1;
编译器的指令重排会保证单线程内程序的执行结果不被改变,但在多线程内则不然,可能会导致多线程运行时的乱序。
volatile关键字可以防止指令重排的发生。
Volatile关键字的原理
应用在多核CPU上运行时,每个CPU都有着自己独立的寄存器,它们会将内存中的数据先加载到各自的寄存器中再进行操作,每次访问时可能直接使用寄存器中的值而不是再去内存中读取。volatile关键词告诉编译器,每次读取这个变量时,都要去读取内存中的值而不是直接读取寄存器中的值。
例:
struct Shop {
int a;
inline Shop(int i) {
a = i;
}
};
void printValue(Shop &shop) {
std::cout << "Thread: " << std::this_thread::get_id() << " Started" << std::endl;
Sleep(2000);
shop.a = 0;
}
int main()
{
Shop shop(1);
std::thread t1(printValue, std::ref(shop));
t1.join();
while (shop.a) {}
std::cout << "Main thread find a != 1" << std::endl;
}
在这段代码中,我们想让主线程在感知到t1线程改变shop.a值的时候退出,但编译出来的结果可能是主线程永远不返回,这是由于主线程一直在访问寄存器中的值,因此,如果我们将第2行改写成
int volatile a;
就可以保证a的变化被主线程读取到了
Volatile关键字不能保证的
volatile关键字不能保证对变量操作的原子性,当cpu对一个变量进行修改时,会先写入寄存器,在寄存器中修改,再写回内存。
因此可能出现:
a初始化为0
thread1 读取a值到寄存器1
theard2 读取a值到寄存器2
thread1 寄存器1中a++
thread2 寄存器2中a++
thread1 寄存器1中a写回内存
thread2 寄存器2中a写回内存
最终a的结果仍为1