C++霧中風景13:volatile解惑

筆者入職百度時,二面面試官的讓我聊聊C++之中的volatile關鍵詞。volatile在Java和C++之中的差別可謂是天差地別,我只是簡單聊了聊Java之中的volatile,面試官對我的回答并不滿意。后續(xù)學習《C++ Prmier》時,對volatile的理解也是云里霧里。入職百度之后,發(fā)現(xiàn)身邊的同學時候對volatile也是誤會頗多。(果然是“面試造核彈,工作擰螺絲”)所以筆者花了一些時間,整理了這篇文章,希望各位C++程序員能徹底厘清volatile。

1.volatile的誤會

volatile這個單詞在英文之中的意思是:易變的,不穩(wěn)定的的含義。所以顧名思義,一旦變量通過了volatile關鍵詞修飾之后,說明變量是易變的和不穩(wěn)定的。而C++之中最大的誤會就是認為volatile關鍵詞與并發(fā)編程有關,至于為何會引起這樣的誤會呢?筆者覺得罪魁禍首可能是下面的原因:

Java中的volatile

volatile影響最為深遠的就是Java之中的功用,筆者第一次接觸這個關鍵詞也是在Java之中。(加上數(shù)目龐大的Java程序員~~)Java之中volatile的效果是:

  • 確保內(nèi)存可見性
    讀和寫一個volatile變量具有全局有序性。每個線程訪問一個volatile變量時都會讀取它在內(nèi)存之中的當前值,而不是使用一個緩存中的值。這樣能夠確保當某線程對volatile變量進行了修改后,后面執(zhí)行的其他線程能看到volatile變量的變動。所以在簡單的多線程程序之中,volatile變量可以起到輕量級的同步作用。但是volatile關鍵字并不保證線程讀寫變量的相對順序,所以適用場景有限。
  • 禁止指令重排序
    指令重排序是JVM 為了提高程序的運行效率,在不影響單線程程序執(zhí)行結果的前提下,對各種指令執(zhí)行的過程進行重新排序和優(yōu)化,來增加程序處理時的并行度。指令重排序在單線程下能夠保證正確,但是在多線程的環(huán)境下就很難確保指令重排序的語義正確。

JDK5引入concurrent包中atomic,JDK6將synchronized關鍵字的性能優(yōu)化后。絕大多數(shù)場景,筆者都不再推薦使用volatile這個關鍵字了。

MSVC 微軟的鍋

早期的 MSVC之中 volatile 具有ReleaseAcquire語義,這我想給許多 C++程序猿造成了誤解。后續(xù)微軟將這個關鍵字做了一個切換:volatile:ms,用加 ms 的修飾來延續(xù)之前的語義。

volatile::ms 的特殊語義

2.volatile 的作用

其實上一節(jié)對volatile 的誤用做了討論。接下來筆者來帶大家看看,實際 volatile 關鍵字到底起到怎么樣的作用。先看如下的代碼:

int n = 10;                                                                                         
int main() {
    int a = n;
    int b = n;
    return 0;
}

我們將這段代碼轉換為匯編代碼,筆者這里使用的** gcc 版本為5.4.0**:

匯編代碼1

接下來,我們將變量添加上 volatile關鍵字,看看會出現(xiàn)什么效果:

int n = 10;                                                                                         
int main() {
    volatile int a = n;
    volatile int b = n;
    return 0;
}

重新編譯這部分代碼轉換為匯編代碼,我們來看看:

匯編代碼2

看到這部分匯編代碼,想必各位應該對 volatile關鍵詞的作用應該心中有數(shù)了。volatile相當于顯式的要求編譯器禁止對 volatile 變量進行優(yōu)化,并且要求每個變量賦值時,需要顯式從寄存器%eax拷貝。volatile 關鍵字在嵌入式編程之中會需要用到,在特定環(huán)境下,寄存器的變量可能會發(fā)生變化。volatile 所以聲明了寄存器部分的數(shù)據(jù)是『易變的』,需要防止編譯器優(yōu)化變量,強制載入寄存器。

但是如果需要實現(xiàn)類似 Java 之中 volatile 的效果呢?可以在代碼之中顯式插入內(nèi)存屏障,讓 CPU 強制刷新寄存器的變量到內(nèi)存之中。

int n = 10;                                                                                         
int main() {
    volatile int a = n;
    asm volatile("" ::: "memory");
    volatile int b = n;
    return 0;
}

現(xiàn)在我們再來看看編譯生成的匯編代碼:

匯編代碼3

由上述的匯編代碼我們可以看到,在添加了內(nèi)存屏障之后,對變量的賦值操作需要顯式的內(nèi)存之中取值。實際 Java 在實現(xiàn) volatile 關鍵字時,也是通過上述語句來實現(xiàn)的。

3.小結

volatile 關鍵字本身在 現(xiàn)代的C++和 Java 之中都不再推薦使用了。在 C++之中有很多對 volatile 的誤用。希望這篇文章能夠幫助大家解惑 volatile ,能夠正確的進行使用。學有不精,如有謬誤,請多多指教~~~

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容