上一篇博客 volatile关键字总结-CSDN博客,我们讲了两个volatile变量可以限制编译器对指令的重排,但是限制不了cpu层面的指令重排
这篇博客开始,我们演示下cpu层面的指令重排,以及如何禁止cpu层面的指令重排
代码如下
- #include
- #include
- using namespace std;
-
- volatile int x = 0;
- volatile int y = 0;
- volatile int a = 0;
- volatile int b = 0;
-
- void threadFun1()
- {
- // 空循环5万次,这样尽量和线程2同时启动,更容易出现x=0,y=0的结果
- for (int i = 0; i < 50000; i++){}
- a = 1;
- x = b;
- }
-
- void threadFun2()
- {
- b = 1;
- y = a;
- }
-
- int main()
- {
- int i = 0;
- while (1)
- {
- i++;
- x = y = a = b = 0;
- thread t1(threadFun1);
- thread t2(threadFun2);
- t1.join();
- t2.join();
- if (x==0 && y == 0)
- {
- cout << "第" << i << "次执行后,发现x=0,且y=0" << endl;
- break;
- }
- else
- {
- cout << "第" << i << "次执行后,x=" << x << ",y=" << y << endl;
- }
- }
- return 0;
- }
对上面的代码解释下:
1. 四个变量x, y, a, b都用volatile关键字修饰,访问时必须从内存读写,确保编译器层面不做任何的指令优化
2. 线程1函数中,先执行a=1,再执行x=b
3. 线程2函数中,先执行b=1,再执行y=a
4. 我们每次先把x,y,a,b置零,然后开启这两个线程
实验如下图
第一次执行5万多次出现了,x=0,且y=0的情况

第二次执行11万多次才出现

按照逻辑,无论线程1先执行,还是线程2先执行,最终两个线程执行完后都不可能出现x=0且y=0的情况
因为出现x=0且y=0,这意味着
要么是 "x=b"先于 "a=1"执行了
要么是 "y=a"先于 "b=1"执行了
也就是虽然四个变量都使用volatile关键字,编译器层面没有出现重排,但是cpu层面出现了指令的重排!
代码如下(仅贴出与实验一不同的地方)
void threadFun1()
{
// 空循环8万次,这样尽量和线程2同时启动,更容易出现x=0,y=0的结果
for (int i = 0; i < 80000; i++){}a = 1;
__sync_synchronize();
x = b;
}void threadFun2()
{
b = 1;
__sync_synchronize();
y = a;
}
以上代码:仅仅是在两个语句之间调用了函数 __sync_synchronize();
注意:这个GCC函数也是linux下用来做内存屏障(memory barrier)的实现原理,防止cpu乱序执行命令
实验结果:

执行了1700多万次测试,都不会出现x=0,且y=0的情况了