volatile的意思是每次都去記憶體讀取值。因為編譯器最佳化的時候,會把記憶體值讀入暫存器,比如迴圈判斷,每個記憶體值是1000,讀入暫存器之後迴圈判斷,這樣速度很快點。
比如一個bool變數,a執行緒一直測試是否為真,為真就執行下面操作,否則死迴圈判斷(可能sleep下)。
b執行緒把這個變數設定為真。
如果不加volatile修飾,a執行緒最佳化的結果是死迴圈,把變數讀入暫存器了,暫存器值顯然不會被其他執行緒改變。
加了這個修飾之後,a執行緒每次都會去記憶體讀取,b執行緒修改之後就能知道值變了。
volatile不一定用於多執行緒,包括中斷,類似的問題如果執行緒在等中斷髮生後某個值改變,也需要這個修飾,否則類似上面的邏輯也是死迴圈。
至於臨界變數,不一定用這個修飾。因為進入臨界變數的只有一個執行緒,不考慮中斷因素,這個值只能獲得臨界變數的執行緒改變。期間該值不會發生變化,即便用暫存器最佳化也是可以的。當然如果還是用死迴圈來檢查,那還是需要volatile修飾。不過既然用了訊號量機制,就沒必要忙等了。
因此是否需要volatile修飾,唯一的判斷是這個變數會不會在當然執行緒使用的過程中被其他執行緒或者中斷改變。如果有可能就需要使用,否則不需要。
常見的場景有使用忙等來同步執行緒。中斷處理變數。
附上測試程式碼,這個坑是我實際工作中遇到過,並不是嵌入式變成,因此比較深刻。這個BUG至少花了我2天時間查詢。
volatile int b1=1;
int b2=1;
int test ()
{
while(b1){
}
while(b2){
來編譯成彙編看看:
gcc -O2 -S t.c(要最佳化編譯)
.L3:
movl b1(%rip), %eax
testl %eax, %eax
jne .L3
movl b2(%rip), %eax
jne .L6 ;第一次判斷是否為0,不為0直接跳L6死迴圈
rep ret
.L6:
.p2align 4,,3
jmp .L6
b2沒有volatile修飾,就第一次會判斷是否為0,之後就直接死迴圈。
附上可執行的測試程式碼
不用最佳化,gcc t2.c -lpthread可以正常退出
用-O2最佳化,程式死迴圈:
只要加上volatile,就正常了.
volatile int b2=1;
volatile的意思是每次都去記憶體讀取值。因為編譯器最佳化的時候,會把記憶體值讀入暫存器,比如迴圈判斷,每個記憶體值是1000,讀入暫存器之後迴圈判斷,這樣速度很快點。
比如一個bool變數,a執行緒一直測試是否為真,為真就執行下面操作,否則死迴圈判斷(可能sleep下)。
b執行緒把這個變數設定為真。
如果不加volatile修飾,a執行緒最佳化的結果是死迴圈,把變數讀入暫存器了,暫存器值顯然不會被其他執行緒改變。
加了這個修飾之後,a執行緒每次都會去記憶體讀取,b執行緒修改之後就能知道值變了。
volatile不一定用於多執行緒,包括中斷,類似的問題如果執行緒在等中斷髮生後某個值改變,也需要這個修飾,否則類似上面的邏輯也是死迴圈。
至於臨界變數,不一定用這個修飾。因為進入臨界變數的只有一個執行緒,不考慮中斷因素,這個值只能獲得臨界變數的執行緒改變。期間該值不會發生變化,即便用暫存器最佳化也是可以的。當然如果還是用死迴圈來檢查,那還是需要volatile修飾。不過既然用了訊號量機制,就沒必要忙等了。
因此是否需要volatile修飾,唯一的判斷是這個變數會不會在當然執行緒使用的過程中被其他執行緒或者中斷改變。如果有可能就需要使用,否則不需要。
常見的場景有使用忙等來同步執行緒。中斷處理變數。
附上測試程式碼,這個坑是我實際工作中遇到過,並不是嵌入式變成,因此比較深刻。這個BUG至少花了我2天時間查詢。
volatile int b1=1;
int b2=1;
int test ()
{
while(b1){
}
while(b2){
}
}
來編譯成彙編看看:
gcc -O2 -S t.c(要最佳化編譯)
.L3:
movl b1(%rip), %eax
testl %eax, %eax
jne .L3
movl b2(%rip), %eax
testl %eax, %eax
jne .L6 ;第一次判斷是否為0,不為0直接跳L6死迴圈
rep ret
.L6:
.p2align 4,,3
jmp .L6
b2沒有volatile修飾,就第一次會判斷是否為0,之後就直接死迴圈。
附上可執行的測試程式碼
不用最佳化,gcc t2.c -lpthread可以正常退出
用-O2最佳化,程式死迴圈:
只要加上volatile,就正常了.
volatile int b2=1;