我們假設有這麼個函數:
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的.