volatile不提供线程同步,仅禁止编译器对易变变量的读写重排序和寄存器缓存;正确同步应使用std::atomic或mutex,典型适用场景为内存映射I/O、信号处理函数修改的sig_atomic_t变量及setjmp/longjmp配合的局部变量。

volatile 不阻止编译器优化,也不提供线程同步;它只告诉编译器:这个变量的值可能在任何时刻被外部(非当前线程、硬件、信号处理函数等)修改,禁止对它的读写做重排序或缓存到寄存器。
volatile 不能替代 std::atomic 或 mutex
常见误解是用 volatile 实现多线程共享标志位(比如 volatile bool stop_requested)。这在 C++ 中是错误的:
- 不保证读写原子性(例如
volatile int的 64 位赋值在 32 位平台可能分两步) - 不建立 happens-before 关系,无法防止指令重排影响其他变量
- 不同线程看到的修改顺序仍不可预测
正确做法是用 std::atomic,它既保证原子性,又提供内存序控制。
真正需要 volatile 的典型场景
以下情况才应使用 volatile:
立即学习“C++免费学习笔记(深入)”;
- 内存映射 I/O 寄存器(如嵌入式中
volatile uint32_t* const reg = reinterpret_cast)(0x40000000); - 被
signal handler修改的全局变量(且仅限sig_atomic_t类型,其他类型即使加volatile也不安全) - 与 setjmp/longjmp 配合使用的局部变量(需加
volatile防止被优化掉)
注意:volatile 对指针本身和所指内容是分开的 —— volatile int* p 表示 *p 易变;int* volatile p 表示 p 的值易变(指针地址可能被外部改写)。
volatile 和编译器优化的具体表现
没有 volatile 时,编译器可能将多次读取合并,或将写入延迟甚至删除:
int flag = 0;
while (flag == 0) {
// 空循环
}
// 编译器可能优化成死循环(flag 值从未在循环内被修改,就只读一次)
加上 volatile int flag = 0; 后,每次循环都会重新从内存读取 flag。但要注意:
- 这不解决并发竞争条件,只是让“轮询”行为可观察
- 现代 CPU 的 cache coherency 协议不保证跨核立即可见,
volatile不触发 cache flush 或 memory barrier - 某些架构(如 ARM)还要求显式内存屏障配合
volatile才能确保顺序
最容易被忽略的一点:C++ 标准从未规定 volatile 具有线程语义;所有依赖它做同步的代码,在严格符合标准的编译器上都属于未定义行为。哪怕它在某个平台“碰巧”工作,也不能当作正确实现。









