1. 使用情况
volatile 与 12-3 中的代码重排序有关。在 12-3 的第三小节,由于添加 set(CMAKE_C_FLAGS "-O3") 导致编译器对汇编指令进行了优化。此时可使用 volatile 关键字,确保被修饰的变量对应的汇编指令不被优化。
#include <stdio.h>
#include <tinycthread.h>
int flag = 0;
int a = 0;
int x = 0;
int T1(void *arg){
a = 2;
flag = 1;
a = a + 3;
return 0;
}
int T2(void *arg){
while (flag == 0){}
x = a * a;
return 0;
};
void TestVisibilityByThread(){
thrd_t t_1;
thrd_t t_2;
// create two threads to run the Counter function
thrd_create(&t_2, T2, NULL);
thrd_create(&t_1, T1, NULL);
// free up resources
thrd_join(t_1, NULL);
thrd_join(t_2, NULL);
printf("a: %d\n", x);
}
int main(){
int count = 0;
while (count < 10){
TestVisibilityByThread();
count++;
}
return 0;
}
12-3 的第三小节代码如上所示。此时查看 a = 2, a = a + 3; 以及 while(flag == 0) 对应的汇编指令(已优化):
对上述代码做部分修改,将 flag 和 a 的变量类型前添加 volatile 关键字。此时查看优化后的 a = 2, a = a + 3; 以及 while(flag == 0) 对应的汇编指令:
volatile int flag = 0;
volatile int a = 0;
可以发现,使用 volatile 关键字修饰的变量,其对应的汇编指令并未被优化。对比 12-3,上图中的指令与未优化的编译指令相同。
2. 注意点
1)volatile 本身并非为并发程序设计,仅仅与代码重排序有关
2)volatile 的目的是禁止编译器优化读写操作
3)volatile 不会保证访问的原子性,操作原子性与否与 volatile 无关
4)与其他语言(例如 Java)的 volatile 不要混淆,不同语言同一关键字功能不同
5)特别地,MSVC 赋予 volatile 强制刷新缓存的语义,保证其可见性(12-3 的第二小节)。但由于其他编译器并没有实现此功能,故不能太过依赖