浅谈Volatile与多线程
生活随笔
收集整理的這篇文章主要介紹了
浅谈Volatile与多线程
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
http://renyan.spaces.eepw.com.cn/articles/article/item/86826
原子性 下面舉個例子說明原子性。i++這看似原子的語句其實有三個操作組成:將該值從內存地址讀取到寄存器中,對寄存器中的值進行加1操作,最后再將新值寫回內存中,正是因為i++并不是原子的,所以如果兩個線程同時進行i++操作的話仍會產生數據競跑,從而導致i的最終值不等于2.在這種情況下,C/C++中的volatile關鍵字根本無法對該操作的原子性提供任何保障。 Volatile int ?i=0; //線程1 I++; //線程2 I++; 順序性 不幸的是,現在C/C++標準中的volatile關鍵字對共享變量操作的順序性也未提供任何保障。以本文中的dekker算法為例:當兩個線程分別執行dekker1和dekker2函數時候,改程序通過對flag1/2和turn的讀寫來實現兩個線程對臨界區中共享變量gCounter的互斥訪問。這個算法的關鍵就在于對flag1/2和turn的讀寫操作是在其寫操作之后進行的,因此它能保證dekker1和dekker2中對gCounterde的操作時互斥的,相當于把gCounter++放到一個臨界區中去了。Dekker算法如下所示: Volatile int flag1 = 0; Volatile int flag2 = 0; Volatile int turn = 1; Volatile int gCounter = 0; Void dekker1() { Flag1 = 1; Turn = 2; While( (flag2 == 1) && ( turn == 2) ){} //進入臨界區 gCounter++; flag1 = 0; //離開臨界區 } Void dekker2() { Flag2 = 1; Turn = 2; While( (flag1 == 1) && ( turn == 2) ){} //進入臨界區 gCounter++; flag2 = 0; //離開臨界區 } 盡管volatile規定編譯器不能對同一變量的所有操作進行亂序優化,但它卻不能阻止編譯器對不同volatile變量間的操作進行亂序優化。例如,編譯器可能把dekker1中的flag2讀操作提到flag1和turn寫操作之前,從而導致對臨界區的互斥訪問失效,最終gCounter++操作就會出現數據競跑現象。線程1在執行flag = 1和turn = 2 之前,由于編譯器對操作的亂序優化,導致先讀取flag2, 此時flag2 = 0,所以線程1直接跳出while循環,要進行gCount++, 此時,暫停線程1的執行,開始執行線程2,線程2也出現和線程1類似的情況,所以線程2也開始執行gCount++。這時就會出現數據競跑。事實上,即使編譯器沒有對這個程序做任何優化,volatile 關鍵字也不能阻止多核CPU對該程序的亂序優化。以常見的x86硬件來說,它可以對不同變量x,y的store x --àload y進行亂序優化,把load y操作提到store x操作之前。這樣的話,dekker1中flag2的讀操作還是有可能會被提到flag1和turn的寫操作之前,最終導致錯誤的計算結果。 那為什么編譯器和多核CPU會對多線程程序做這樣的亂序優化呢?因為從單核的視角來看,flag1 和 flag2,turn的讀寫操作之間沒有任何依賴關系的,使用編譯器/CPU當然可以對他們進行亂序優化以隱藏一部分的內存訪問延遲,從而更好的利用CPU里的流水線。換句話說,這樣的優化雖從單線程的角度來講沒有錯,但卻違反了設計這個多線程算法時所期望的多線程語義。要是解決這個問題,我們需要解決這個問題,我們需要自己添加內存柵欄以顯式保證順序性,或者干脆去別去實現這樣的算法,轉而使用類似pthread_mutex_lock這樣的加鎖操作來實現互斥訪問。 綜合上述,由于現有的C/C++標準中并沒有對volatile添加原子性和順序性的語義,所以絕大部分C/C++程序中使用volatile來進行多線程同步的用法是錯誤的。其實,我們之所以想用volatile變量進行同步,無非是因為鎖,條件變量等方式的開銷太大,所以想有一種輕量級的,高效的同步機制。
總結
以上是生活随笔為你收集整理的浅谈Volatile与多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ajax的原理和应用
- 下一篇: sgi allocate