使用volatile限定的数组并发修改是否具备原子性和线程可见性?
代码中volatile限定的作用说明
你写的volatile unsigned char *volatile key[6]中两个volatile分别约束不同对象:
- 靠近
unsigned char的第一个volatile:表示指针指向的unsigned char数据是易变的,编译器不能优化对应读写操作,每次读必须从内存地址取值,每次写必须立刻回写内存,不能暂存在寄存器中。 - 靠近
*的第二个volatile:表示key数组中存储的每个指针变量本身是易变的,编译器同样不能优化对这些指针本身的读写。
问题1:能否保证多线程下的可见性?
不能。volatile的设计初衷是处理内存映射IO、中断这类硬件交互场景,并非为多线程场景设计:
- 它只能约束编译器的行为,不会在生成的机器码中插入CPU内存屏障指令,无法解决多核CPU的缓存一致性问题。某一个CPU核修改了数组元素后,修改可能仅停留在当前核的私有缓存中,其他核的缓存不会主动失效,对应线程读到的仍然是旧的缓存值。
- 即便是C11及之后的标准,volatile也不提供跨线程的可见性保证,跨线程可见性需要使用C11提供的
_Atomic类型或者手动插入内存屏障才能实现。
问题2:能否保证操作的原子性?
完全不能。volatile没有任何原子性相关的语义:
- 哪怕是单个char的读写操作,C标准也从未规定它是原子的,仅大部分主流架构下对齐的char读写默认是原子的,这和volatile没有任何关联。
- 如果是读改写这类复合操作(比如
*key[0] += 1、*key[0] ^= 0xFF这类),哪怕操作对象是char类型,整个过程也会被拆分为多条CPU指令,执行中途完全可能被其他线程打断,进而触发竞态条件。
额外提示
你给出的代码存在类型不匹配问题:你定义的key是存储6个volatile unsigned char*指针的数组,但初始化列表给的是整数字面量,相当于直接把常量值作为内存地址赋值给指针。如果你实际想要定义的是存储6个unsigned char值的数组,正确写法应为volatile unsigned char key[6] = { 0xD4 ,0x32 ,0xF8 ,0x0A ,0x10 ,0xE5 };,无论哪种定义,前述的结论都成立。
内容的提问来源于stack exchange,提问作者eztam
相关产品推荐
相关产品推荐

