C++霧中風景13:volatile解惑
筆者入職百度時,二面面試官的讓我聊聊C++之中的volatile關鍵詞。volatile在Java和C++之中的差别可謂是天差地别,我隻是簡單聊了聊Java之中的volatile,面試官對我的回答并不滿意。後續學習《C++ Prmier》時,對volatile的了解也是雲裡霧裡。入職百度之後,發現身邊的同學時候對volatile也是誤會頗多。(果然是“面試造核彈,工作擰螺絲”)是以筆者花了一些時間,整理了這篇文章,希望各位C++程式員能徹底厘清volatile。
1.volatile的誤會
volatile這個單詞在英文之中的意思是:易變的,不穩定的的含義。是以顧名思義,一旦變量通過了volatile關鍵詞修飾之後,說明變量是易變的和不穩定的。而C++之中最大的誤會就是認為volatile關鍵詞與并發程式設計有關,至于為何會引起這樣的誤會呢?筆者覺得罪魁禍首可能是下面的原因:
Java中的volatile
volatile影響最為深遠的就是Java之中的功用,筆者第一次接觸這個關鍵詞也是在Java之中。(加上數目龐大的Java程式員~~)Java之中volatile的效果是:
確定記憶體可見性
讀和寫一個volatile變量具有全局有序性。每個線程通路一個volatile變量時都會讀取它在記憶體之中的目前值,而不是使用一個緩存中的值。這樣能夠確定當某線程對volatile變量進行了修改後,後面執行的其他線程能看到volatile變量的變動。是以在簡單的多線程程式之中,volatile變量可以起到輕量級的同步作用。但是volatile關鍵字并不保證線程讀寫變量的相對順序,是以适用場景有限。
禁止指令重排序
指令重排序是JVM 為了提高程式的運作效率,在不影響單線程程式執行結果的前提下,對各種指令執行的過程進行重新排序和優化,來增加程式處理時的并行度。指令重排序在單線程下能夠保證正确,但是在多線程的環境下就很難確定指令重排序的語義正确。
JDK5引入concurrent包中atomic,JDK6将synchronized關鍵字的性能優化後。絕大多數場景,筆者都不再推薦使用volatile這個關鍵字了。
MSVC 微軟的鍋
早期的 MSVC之中 volatile 具有Release和Acquire語義,這我想給許多 C++程式猿造成了誤解。後續微軟将這個關鍵字做了一個切換:volatile:ms,用加 ms 的修飾來延續之前的語義。
volatile::ms 的特殊語義
2.volatile 的作用
其實上一節對volatile 的誤用做了讨論。接下來筆者來帶大家看看,實際 volatile 關鍵字到底起到怎麼樣的作用。先看如下的代碼:
int n = 10;
int main() {
int a = n;
int b = n;
return 0;
}
我們将這段代碼轉換為彙編代碼,筆者這裡使用的** gcc 版本為5.4.0:
彙編代碼1
接下來,我們将變量添加上 volatile**關鍵字,看看會出現什麼效果:
volatile int a = n;
volatile int b = n;
return 0;
重新編譯這部分代碼轉換為彙編代碼,我們來看看:
彙編代碼2
看到這部分彙編代碼,想必各位應該對 volatile關鍵詞的作用應該心中有數了。volatile相當于顯式的要求編譯器禁止對 volatile 變量進行優化,并且要求每個變量指派時,需要顯式從寄存器%eax拷貝。volatile 關鍵字在嵌入式程式設計之中會需要用到,在特定環境下,寄存器的變量可能會發生變化。volatile 是以聲明了寄存器部分的資料是『易變的』,需要防止編譯器優化變量,強制載入寄存器。
但是如果需要實作類似 Java 之中 volatile 的效果呢?可以在代碼之中顯式插入記憶體屏障,讓 CPU 強制重新整理寄存器的變量到記憶體之中。
volatile int a = n;
asm volatile("" ::: "memory");
volatile int b = n;
return 0;
現在我們再來看看編譯生成的彙編代碼:
彙編代碼3
由上述的彙編代碼我們可以看到,在添加了記憶體屏障之後,對變量的指派操作需要顯式的記憶體之中取值。實際 Java 在實作 volatile 關鍵字時,也是通過上述語句來實作的。
3.小結
volatile 關鍵字本身在 現代的C++和 Java 之中都不再推薦使用了。在 C++之中有很多對 volatile 的誤用。希望這篇文章能夠幫助大家解惑 volatile ,能夠正确的進行使用。學有不精,如有謬誤,請多多指教~~~
原文位址
https://www.cnblogs.com/happenlee/p/10561190.html