volatile与多线程
发布者:
Bo-tao Zhang
C/C++中volatile的传统用途
保证变量的读写操作直接作用于内存
即每次读的时候都直接去该变量的内存地址中读,每次写的时候都直接写到该变量的内存地址中去。
看下面的例子,如果没加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的这种特性 来对两线程进行的同步。比如一个经典的错误用法是:用一个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,也不容许写写乱序。
非标准的volatile
前面讲了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
传统功能的拓展。
睿初科技软件开发技术博客,转载请注明出处
blog comments powered by Disqus
发布日期
标签
最近发表
- volatile与多线程
- TDD practice in UI: Develop and test GUI independently by mockito
- jemalloc源码解析-核心架构
- jemalloc源码解析-内存管理
- boost::bind源码分析
- 小试QtTest
- 一个gtk下的目录权限问题
- Django学习 - Model
- Code snippets from C & C++ Code Capsule
- Using Eclipse Spy in GUI products based on RCP
文章分类
- cpp 3
- wxwidgets 4
- swt/jface 1
- chrome 3
- memory_management 5
- eclipse 1
- 工具 4
- 项目管理 1
- cpplint 1
- 算法 1
- 编程语言 1
- python 5
- compile 1
- c++ 7
- 工具 c++ 1
- 源码分析 c++ 3
- c++ boost 2
- data structure 1
- wxwidgets c++ 1
- template 1
- boost 1
- wxsocket 1
- wxwidget 2
- java 2
- 源码分析 1
- 网路工具 1
- eclipse插件 1
- django 1
- gtk 1
- 测试 1
- 测试 tdd 1
- multithreading 1