volatile
关键字是个老生常谈的话题了,C/C++面试必问。但还是有很多人搞不懂volatile
是干什么的,定义都知道:volatile 关键字在 C 和 C++ 语言中被用来指示编译器,不要对该变量进行优化,被其修饰的变量可能会在程序的控制之外被改变。
先说结论,有了C++11引入的std::atomic
,我们就已经不需要volatile
了。volatile
常用于C++11之前的时代,或者使用C语言的嵌入式项目里。
volatile
实质上解决的是内存顺序的问题,关于内存顺序的详解可以先看下大白话C++之:深入理解多线程内存顺序(Memory Order)。
既然volatile的作用不好理解,那我们干脆直接从原理的角度看下编译器会对volatile
做什么。
当一个变量被定义为volatile
后,实质上编译器会做3件事情:
- 在volatile变量的所在地方插入一个内存屏障,前面的指令不能重排到屏障之后,后面的指令也不能重排到屏障之前。
- 读取volatile变量时,直接从内存中读,而不是从CPU Cache中读。
- 更新volatile变量时,不仅要更新CPU Cache中的值,还要更新到内存里,强制刷新其他CPU核心里的Cache,让其他CPU核心能立马看到这个volatile变量的最新值。
此处的CPU Cache指L1 Cache
, L2 Cache
, L3 Cache
等,如下图所示,不了解CPU Cache的需要移步大白话C++之:深入理解多线程内存顺序(Memory Order)了解下😁。
Talk is cheap, let’s see the code.
#include <iostream>
#include <thread>
#include <chrono>
// 模拟硬件信号状态,使用volatile关键字
volatile bool hardwareSignal = false;
// 硬件信号检测函数
void detectHardwareSignal() {
while (true) {
// 如果hardwareSignal没有volatile属性,
// 此处读取hardwareSignal就会从CPU Cache里读,
// 无法及时看到其他CPU核心更新的hardwareSignal最新值。
//
// 但是因为volatile属性,会从内存读取hardwareSignal的值。
if (hardwareSignal) {
std::cout << "Hardware signal detected!" << std::endl;
hardwareSignal = false; // 重置信号
}
// 延迟一段时间,模拟持续检测
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
// 主函数
int main() {
// 启动线程,模拟硬件信号检测
std::thread signalThread(detectHardwareSignal);
// 模拟从其他线程触发硬件信号
// main函数本身就是一个线程
std::this_thread::sleep_for(std::chrono::seconds(2));
// 如果hardwareSignal没有volatile属性,
// 此处只会更新CPU Cache里的hardwareSignal,
// detectHardwareSignal线程里看到的hardwareSignal仍然是false。
//
// 但是因为volatile属性,此处会更新内存里hardwareSignal的值。
hardwareSignal = true;
// 等待线程完成
signalThread.join();
return 0;
}
读过大白话C++之:深入理解多线程内存顺序(Memory Order)的同学看到这里,其实应该已经明白了,volatile
变量相当于std::memory_order
里的memory_order_seq_cst
,只是不保证操作的原子性,volatile
常常需要与pthread_mutex_t
一起搭配食用。
所以当C++11引入std::atomic
后,我们就可以使用std::atomi
搭配std::memory_order
来取代volatile
和pthread_mutex_
的组合啦。