volatile的进一步深入理解
一、relaxed
是什么意思?
在这里,relaxed
指的是内存访问的顺序不会被强制保证,也就是说:
编译器和 CPU 都可以对这次访问进行重排序优化。
举个例子:
// 假设这是 SoC 的寄存器地址
#define DP_CTL ((volatile uint32_t*)0xF9008000)
#define DP_START ((volatile uint32_t*)0xF9008004)*DP_CTL = 0x1;
*DP_START = 0x1;
你想让它“先 enable,后 start”,但如果底层访问是 relaxed 的(比如 writel_relaxed()
),那 CPU 可能这么执行:
-
实际指令顺序:
start → enable
这在硬件交互中是灾难性的,因为寄存器顺序不对,设备可能根本不启动。
所以“relaxed”访问:
-
没有内存屏障(barrier)
-
不能保证访问顺序
-
适合“没有依赖关系”的访问,比如配置缓存
-
不适合用于需要顺序写入的硬件寄存器
在 Linux 内核中:
writel(val, reg); // 带 barrier(强顺序)
writel_relaxed(val, reg); // 不带 barrier(弱顺序)
二、dead store elimination
是什么?
它是编译器的一种优化策略:
编译器会删除写入但从未读取的变量,因为它“看起来没用”。
举例:
int a = 42;
a = 43;
编译器会优化掉 a = 42
,因为后面立马又改成了 43,前一次写入是“死掉的写入”。
问题来了:
如果这个变量是个 映射的寄存器地址,或会被其他线程读取,那这次“看起来没用”的写入其实是有意义的!
加上 volatile
后,编译器就不会删了!
三、为什么volatile ≠ 原子性
,但又说它能用于多线程共享变量?
volatile 能用于多线程共享变量吗?
是的,可以用于某些简单的多线程同步场景,比如状态标志、轮询退出等:
volatile bool stop = false;void worker_thread() {while (!stop) {// doing work}
}
主线程设置 stop = true;
,worker 线程能马上看到。
但是,它不能保证原子性
举个例子:
volatile int counter = 0;thread 1: counter++;
thread 2: counter++;
每个 counter++
实际是三步:
load → increment → store
多个线程可能交叉执行,造成 丢失更新(lost update)的问题!
所以:
volatile
适合用在 “单向通知” 的变量(比如标志位)
不适合用在 “读改写” 的变量(比如计数器、队列头尾)
正确方式:使用 atomic
或加锁
C++11 起提供了 std::atomic<T>
,它:
-
保证线程间的可见性(类似
volatile
) -
还提供原子性(atomicity)
-
可选 memory ordering 模型(acquire/release/relaxed)
总结对比表:
特性 | volatile | std::atomic / 加锁 |
---|---|---|
防编译器优化 | ✅ 是 | ✅ 是 |
防 CPU 重排序 | ❌ 否 | ✅ 可选(acquire 等) |
保证原子性 | ❌ 否 | ✅ 是 |
多线程下安全 | ⚠️ 有条件 | ✅ 是 |
驱动开发适用 | ✅ 非常适合 | ❌ 不适合 |
小结一句话:
-
volatile
保证“每次访问都去读/写内存”,防止编译器优化,但不保证原子性或顺序性 -
relaxed
意味着“CPU 和编译器都可以优化访问顺序”,适合无依赖场景 -
需要 顺序访问或多线程安全 时,必须加上 memory barrier 或 std::atomic