volatile底層實現(CPU的緩存一致性協議MESI)


 

CPU的緩存一致性協議MESI
在多核CPU中,內存中的數據會在多個核心中存在數據副本,某一個核心發生修改操作,就產生了數據不一致的問題,而一致性協議正是用於保證多個CPU cache之間緩存共享數據的一致性。

cache的寫操作
write through 寫通

每次CPU修改cache中的內容會立即更新到內存,也就意味着每次CPU寫共享數據,會導致總線事務,因此這種方式常常會引起總線事務的競爭,雖然后高的一致性但是效率非常低。

write back 寫回

每次CPU修改了cache中的數據,不會立即更新到內存,而是等到cache line在某一個必須或合適的實際才會更新到內存。

寫失效
當一個CPU修改了數據,如果其他CPU有該數據,則通知其為無效;

寫更新
當一個CPU修改了數據,如果其他CPU有該數據,則通知其更新;

cache line

cache line是cache與內存數據交換的最小單位,根據操作系統一般是32或64byte,在MESI協議中狀態 可以是M、E、S、I,地址則是cache line中映射的內存地址,數據則是從內存中讀取的數據。

工作方式

當CPU從cache中讀取數據時候,會比較地址是否相同,如果相同則檢查cache line的狀態,再決定該數據是否有效,無效則從主存中獲取數據,或根據一致性協議發生一次cache-to-cache的數據推送。

工作效率

當CPU能夠從cache中拿到有效數據的時候,消耗幾個CPU cycle,如果發生cache miss也就是緩存中沒有數據需要從主存中讀取,則會消耗幾十上百個CPU cycle。

狀態介紹
MESI協議將cache line的狀態分為modify、exclusive、shared、invalid分別是修改、獨占、共享、失效

狀態 描述
M(modify) 當前CPU剛修改完數據的狀態,當前CPU擁有最新數據,其他CPU擁有失效數據,而且和主存數據不一致
E(exclusive) 只有當前CPU中有數據,其他CPU中沒有改數據,當前CPU的數據和主存的數據是一致的
S(shared) 當前CPU和其他CPU中都有共同的數據,並且和主存中的數據一致;
I(invalid) 當前CPU中的數據失效,數據應該從主存中獲取,其他CPU中可能有數據也可能無數據;當前CPU中的數據和主存中的數據被認為不一致。
M和E狀態下的Cache Line數據是獨有的,不同點在於M狀態的數據時dirty和內存的不一致,E狀態下數據和內存是一致的;

MESI協議狀態遷移
在MESI協議中,每個Cache控制器不僅知道自己的讀寫操作,而且也監聽其他Cache的讀寫操作,每個Cache line所處的狀態根據本核和其他核的讀寫操作在4個狀態之間進行遷移。

分為以下四個操作:

讀本cache LocalRead;
寫本cache LocalWrite;
讀其他cache Remote Read;
寫其他cache Remote Write;

內存屏障
編譯器和CPU可以保證輸出結果一樣的情況下對指令重排序,使性能得到優化,插入一個內存屏障,相當於告訴CPU和編譯器限於這個命令的必須先執行,后於這個命令的必須后執行。

內存屏障的另一個作用是強制更新一次不同CPU的緩存,這意味着如果你對一個volatile字段進行寫操作,你必須知道:

一旦你完成寫入,任何訪問這個字段的線程將會得到最新的值;
在你寫入之前,會保證所有之前發生的事已經發生,並且任何更新過的數據值也是可見的,因為內存屏障會把之前的寫入值都刷新到緩存。
Volatile是如何保證可見性的?
加入volatile關鍵字時,會多出一個lock前綴指令,lock前綴指令實際上相當於一個內存屏障,它有三個功能:

確保指令重排序時不會把其后面的指令重排到內存屏障之前的位置,也不會把前面的指令排到內存屏障后面,即在執行到內存屏障這句指令時,前面的操作已經全部完成;
將當前處理器緩存行的數據立即寫回系統內存(由volatile先行發生原則保證);
這個寫回內存的操作會引起在其他CPU里緩存了該內存地址的數據無效。寫回操作時要經過總線傳播數據,而每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置為無效狀態,當處理器要對這個值進行修改的時候,會強制重新從系統內存里把數據讀到處理器緩存(也是由volatile先行發生原則保證);
緩存一致性協議有多種,但是日常處理的大多數計算機設備都屬於”嗅探(snooping)”協議,它的基本思想是:
所有內存的傳輸都發生在一條共享的總線上,而所有的處理器都能看到這條總線:緩存本身是獨立的,但是內存是共享資源,所有的內存訪問都要經過仲裁(同一個指令周期中,只有一個CPU緩存可以讀寫內存)。
CPU緩存不僅僅在做內存傳輸的時候才與總線打交道,而是不停在嗅探總線上發生的數據交換,跟蹤其他緩存在做什么。所以當一個緩存代表它所屬的處理器去讀寫內存時,其它處理器都會得到通知,它們以此來使自己的緩存保持同步。只要某個處理器一寫內存,其它處理器馬上知道這塊內存在它們的緩存段中已失效。

** 反復思考IA-32手冊對lock指令作用的這幾段描述,可以得出lock指令的幾個作用:
1、鎖總線,其它CPU對內存的讀寫請求都會被阻塞,直到鎖釋放,不過實際后來的處理器都采用鎖緩存替代鎖總線,因為鎖總線的開銷比較大,鎖總線期間其他CPU沒法訪問內存
2、lock后的寫操作會回寫已修改的數據,同時讓其它CPU相關緩存行失效,從而重新從主存中加載最新的數據
3、不是內存屏障卻能完成類似內存屏障的功能,阻止屏障兩遍的指令重排序
由於效率問題,實際后來的處理器都采用鎖緩存來替代鎖總線,這種場景下多緩存的數據一致是通過緩存一致性協議來保證的 **

問題
既然CPU有了MESI協議可以保證cache的一致性,那么為什么還需要volatile這個關鍵詞來保證可見性(內存屏障)?或者是只有加了volatile的變量在多核cpu執行的時候才會觸發緩存一致性協議?

兩個解釋結論:

多核情況下,所有的cpu操作都會涉及緩存一致性的校驗,只不過該協議是弱一致性,不能保證一個線程修改變量后,其他線程立馬可見,也就是說雖然其他CPU狀態已經置為無效,但是當前CPU可能將數據修改之后又去做其他事情,沒有來得及將修改后的變量刷新回主存,而如果此時其他CPU需要使用該變量,則又會從主存中讀取到舊的值。而volatile則可以保證可見性,即立即刷新回主存,修改操作和寫回操作必須是一個原子操作;
正常情況下,系統操作並不會進行緩存一致性的校驗,只有變量被volatile修飾了,該變量所在的緩存行才被賦予緩存一致性的校驗功能。
volatile的使用場景
狀態標志(開關模式)
雙重檢查鎖定
需要利用順序性
volatile和synchronized的區別
使用上的區別
volatile只能修飾變量,synchronized只能修飾方法和語句塊;

對原子性的保證
synchronized可以保證原子性,volatile不能保證原子性;

對可見性的保證
都可以保證可見性,但實現原理不同,volatile對變量加了lock,synchronized使用monitorEnter和monitorExit;

對有序性的保證
都可以保證有序性,但是synchronized並發退化到串行;

其他
synchronized引起阻塞;
volatile不會引起阻塞;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM