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

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