赞
踩
目录
阻止编译器对变量做激进的优化。比如直接去掉某变量相关的指令。
即每次读的时候都直接去该变量的内存地址中读,每次写的时候都直接写到该变量的内存地址中去。 看下面的例子,如果没加volatile
的话,下面这段代码中变量flag
的读操作可能会被编译器优化掉,但如果加了volatile
则不会。
- bool flag = false;
- void fun()
- {
- while(!flag)
- {
- // do something
- }
- }
-
- // flag未加volatile 被优化后相当于下面这样
- bool flag = false;
- void func()
- {
- if (!flag) // read only once
- {
- while(true)
- {
- // do something
- }
- }
- }

注意:只能够保证volatile
变量之间的操作顺序,不保证volatile
与非volatile
变量之间的操作顺序不被编译器改变。 第一个例子,两个变量都没加volatile
, 他们的操作顺序被编译器打乱。
第二个例子,只有一个变量是volatile
, 另一个不是。 它们的操作顺序还是被改变。
第三个例子,两变量都是volatile
, 它们的操作顺序未被改变。
注意: 虽然volatile
变量之间的操作顺序不会被编译器打乱,但是还是有可能被CPU打乱, 具体要看CPU的内存模型。这也是volatile
不能用于线程同步的原因。
- int func()
- {
- int x = 1;// 如果未加volatile的话,x, y两变量会直接被优化掉
- int y = 2;
- int a = x + y;
- printf("a = %d", a);
- }
因为volatile能够保证变量的读写操作直接作用于内存。所以很多人就想当然的认为可以用volatile的这种特性 来对两线程进行的同步。比如一个经典的错误用法是:用一个volatile flag来同步两个线程。一个线程准备好 数据之后设置flag的值,另一个线程则不停的check flag 的值,根据flag的值来判断数据是否可读。听起来好像 没什么问题,但是大家想一想,万一其中一个线程内的指令被打乱使得在数据还没准备好就设置了flag的值, 这时另一个线程就会读到一些没有初始化的数据,从而导致意想不到的错误。 看下面这个例子:
- volatile int flag = 0;
- volatile int data;
- void Write() // thread one
- {
- data = 2;
- flag = 1;
- }
- void Read() // thread two
- {
- while(!flag)
- {
- printf("data = %d\n", data);
- }
- }
上面这段代码在X86上没问题,在其他平台上则可能会出现错误。之所以错误是因为 函数write里面的操作data = 2;flag = 1;的执行顺序可以被CPU打乱。注意,我说的是可能被打乱, 至于到底会不会被打乱得看CPU的内存模型。在X86下面是不会被打乱的,因为在X86里面不容 许写写乱序,只容许写读乱序。但是在PowerPC下则会乱序。 下面这张表格记录了各个CPU中可能会被乱序的操作。从表中不难看出在X86下,不容许写写乱序。 注意:X86_64 属于AMD64,也不容许写写乱序。
前面讲了C/C++中volatile
的作用, 传统的volatile
对指令的执行顺序无法保证。但是Visual C++和Java里面 对传统的volatile
功能进行了拓展。在Visual C++里面,volatile
变量的读操作具有Acquire语义而写操作具有 Release语义。Acquire语义是指:紧跟在volatile
变量的读操作之后的所有操作必须等到volatile
变量的读操作 执行完之后才开始执行,绝不容许将其提到volatile
的读操作之前执行。Release语义是指:volatile
变量的 写操作之前的所有操作必须在volatile
变量的写操作之前执行,绝不容许提到volatile
变量的写操作之后执行。 正因为如此,所以下面这段代码在visual C++ 中也是安全的。在write 线程中,是对两个volatile
变量进行写操作, 因为写操作具有Acquire 语义所以不能乱序,而在Read线程中是对两个volatile
变量进行读操作,因为读操作具有 Relese语义,所以也不能乱序。因此,下面这段代码在visual C++ 中也是安全的。
- volatile int flag = 0;
- volatile int data;
- void Write()
- {
- data = 2;
- flag = 1;
- }
- void Read()
- {
- while(!flag)
- {
- printf("data = %d\n", data);
- }
- }
以上讲到了C/C++中volatile
的三种基本用法以及volatile
在多线程中的误用。 还顺便提到了Visual C++ 和Java中对volatile
传统功能的拓展。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。