我们假设有这么个函数:
void Func(int& k)
{
while (k!=100)
{}
}
如果int引用k不等于100,则在函数内部不断地循环,我们且不论这个函数性能和作用如何,单说这个函数,
它的目的很明确,即如果k!=100,则运行这个函数的线程就卡在这里了.
下面我们写main.cpp:
void Do(int& k)
{
while (k!=100)
{}
}
int main()
{
using namespace std;
int a=10;
Do(a);
return 1;
}
显然的,在单线程环境下这个main()直到世界末日也不会return了,由于编译器知道了a的初始值为100,并且没人能改变它,所以
它完全可以将Do函数进行优化,使其在编译完之后形式如下:
void FastDo(int& k)
{
while (true);
}
因为在单线程情况下运行到这个位置,不可能有人能修改k的值,因此每次循环根本不需要再次读取k的值.要知道读取内存是很慢的.
现在问题出现了,在多线程环境下,int a的值有可能在任何时候被其他一些线程改变,因此上面的优化方法实际是不正确的.
所以C++标准添加了volatile关键字,用来表述一个变量是"易变的",可能"意想不到地被改变",比如被另一条线程改变.
如果我们添加了volatile关键字:
void Do(volatile int& k)
{
while (k!=100)
{}
}
int main()
{
using namespace std;
volatile int a=10;
Do(a);
return 1;
}
这时,编译器在执行while(k!=100)的时候,将不再认为k的值一直是固定的,所以不会进行上文所提到的优化.保证程序逻辑的正确.
需要注意的是,volatile关键字和多线程,原子操作,加锁什么的没有任何关系,加上volatile关键字不会使变量自动变为线程安全的.
以上是volatile的概念,下面是一些重要的概念:
上面的程序实际上并没有很好的优化,我们有个问题,假如这种情况下,会发生什么事情呢:
void Do(volatile int& k)
{
while (k!=100)
{}
}
int main()
{
using namespace std;
int a=10; //注意a不再是volatile的
Do(a);
return 1;
}
以上代码中,a本身不是volatile的,但Do()的输入是volatile的,把a作为Do()函数的输入,会发生什么事情呢?
这意味着,a的值不是易变的,但a的值在Do()函数内部是volatile的,也就是说,main函数中可以随便对a进行
各种单线程的优化,而直到a进入Do()函数中,a便被认为是易变的.我们有:
volatile int& = int //没问题
volatile int* = &int //没问题
一个不易变的变量可以通过一个volatile引用或者volatile指针指向.这时当我们使用这个引用或指针时,编译器便认为
我们在操作一个易变的变量.如果我们依然操作变量本身,则编译器认为这个变量不是易变的..
这样做有什么好处呢? 我直接声明个 volatile int a;不就得了? 要知道加入volatile关键字后,对象的每一次操作都要
读取变量所在的内存,这就会大大下降操作的速度.对多线程编程来说,这样做效率较低,因为数条线程之间并不需要
每时每刻都同步.
需要注意一件事:
volatile int* : 意味着指针所指向的对象是volatile的
int* volatile : 意味着指针本身是volatile的
另外,这俩个事情不能干,编译器也不让你干:
int& = volatile int
int* =& volatile int
你不能用一个非volatile的引用或者指针指向一个volatile的对象,因为从逻辑上,volatile对象是易变的,既然是易变的就不能用
不易变的引用或指针去指向它.如果你非要这么做,可以这样:
volatile int a;
//.........................
int* p=(int*)&a;
这样可以,但有点hack式编程的味道,不大舒爽的感觉.
对一个类来说,类成员函数也可以使用volatile来声明,比如:
class ListNode
{
public:
//...........
void Do();
void ThreadDo() volatile;
private:
int* a;
}
加入了volatile关键字的类成员函数,意味着在这个函数内部,类成员变量全部都是volatile的.
当然,volatile不能重载:
void Do();
void Do() volatile ; //这样不对
因为编译器才不知道你要调用的情况是不是volatile的.