详解volatile关键字

我们假设有这么个函数:

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的.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章