volatile關鍵字?MESI協議?指令重排?內存屏障?這都是啥玩意


一、摘要

三級緩存,MESI緩存一致性協議,指令重排,內存屏障,JMM,volatile。單拿一個出來,想必大家對這些概念應該有一定了解。但是這些東西有什么必然的聯系,或者他們之間究竟有什么前世今生想必是困擾大家的一個問題。
為什么有了MESI協議,我們還需要volatile?
內存屏障的由來?
指令重排帶來的問題?
下面我們通過分析每一個技術的由來,以及帶來的負面影響,跟大家探討一下這些技術之間的聯系。具體每個關鍵詞相關文章也很多不再贅述,只談個人理解。

二、三級緩存篇

1,三級緩存的由來

CPU的發展很快,幾乎18-24個月,CPU的計算能力就能翻一番,然而內存的發展卻相對緩慢,以至於內存的讀寫速度遠遠跟不上CPU的計算能力,也就是說,在CPU和內存的資源交換中,CPU常常需要等待內存,而浪費了大量的計算能力。三級緩存的出現正是為了彌補內存的慢和CPU的快而誕生的產物。
CPU將不再直接與內存進行數據的交換而是在二者之間加入一個緩存以解決這種不協調而導致的資源浪費情況。當然了緩存一定是比內存的讀寫速度更快的,不然也沒有這個必要了。那是不是我們直接用緩存就不用內存了,呵呵,錢錢錢。
所謂的三級緩存正是誕生在這樣的一個背景下。
三級緩存分為三種:L1 Cache,L2 Cache,L3 Cache

內存以及三級緩存的響應時間如上所示,由此可知內存和緩存的差距還是很大的,當時硬件價格和上圖響應時間是成正比的。響應時間越快,價格越貴。

2,CPU緩存架構

不同的CPU廠商的架構也有些不同,在這里只介紹流行的緩存架構

 

 

緩存的意義

1 時間局限性:如果某個數據被訪問,那么它在不久的將來有可能被在次訪問
2 空間局限性:如果某個緩存行的數據被訪問,那么與之相鄰的緩存行很快可能被訪問到

 

3,緩存帶來的問題

緩存的加入是為了解決CPU運算能力和內存讀寫能力的不匹配問題,簡單來說就是為了提升資源利用率。那么在多CPU(一個CPU對應一個或者多個核心)或者多核心下,每個核心都會有一個一級緩存或者二級緩存,也就是說一二級緩存是核心獨占的(類似JMM模型,線程的工作內存是線程獨占的,主內存是共享的)而三級緩存和主內存是共享的,這樣就將導致CPU緩存一致性問題。為了解決這種不一致,大名鼎鼎的MESI協議隨之而來。

三、MESI緩存一致性協議篇

1,MESI協議

MESI協議的由來上個章節已經介紹,在此不做過多介紹

MESI:Modified(修改),Exclusive(獨占),Shared(共享),Invalid(無效)由以上數據的四種狀態的首字母而來。

在緩存中數據的存儲單元是緩存行(Cache line),主流的CPU緩存行都是64個字節。緩存行的四種狀態由兩個字節標識。

M(Modified)

這行數據有效,數據被修改了,和內存中的數據不一致,數據只存在於本Cache中。

E(Exclusive)

這行數據有效,數據和內存中的數據一致,數據只存在於本 Cache 中。

S(Shared)

這行數據有效,數據和內存中的數據一致,數據存在於很多 Cache 中。

I(Invalid)

這行數據無效。

 

 

當一個緩存行的狀態調整,另外一個緩存行需要調整的狀態如下:

 

M

E

S

I

M

×

×

×

E

×

×

×

S

×

×

I


模擬緩存行獨占(E)過程:

核心core1發出加載數據指令,通過bus從內存中加載了一個數據A進入到該核心的緩存行,並且發現該數據是沒有被別的核心加載的,此時該核心將會將該數據A狀態置為獨占即E狀態。
模擬緩存行共享(S)過程:

上面core1已經獨占了數據A,此時core2也發指令要讀取數據A,此時發現該數據已經被core1占有,然后通知core1“我也要使用該數據”,所以core1將數據A狀態改為S即 E >>> S。
模擬緩存行修改(M)過程:

假如有三個核心:core 1,core 2分別緩存了數據A。因為多個核心都緩存了該數據,即在各個核心中該數據的狀態都是Shared。
此時core 1需要修改數據A。
core 1:內核計算完成,通過指令寫入緩存行,數據A的狀態將從S >>> M
core 2:core1將通知緩存了該數據的核心“我修改了該數據”,所以core2會將該緩存置為無效。因為數據已經發生了變化,core2緩存的數據A將不再有任何意義。
埋個伏筆,以上過程提到的“通知”的過程可能是很耗時的,在此期間core1將會處於等待回應。浪費了core1的計算能力。

2,MESI協議帶來的問題

多個core的緩存狀態置換是需要消耗時間的,導致內核在此期間將無事可做。甚至一旦某一個內核發生阻塞,將會導致其他內核也處於阻塞,從而帶來性能和穩定性的極大消耗。
所以這時指令重排開始發揮它的價值。想想這種等待有時是沒有必要的,因為在這個等待時間內內核完全可以去干一些其他事情。即當內核處於等待狀態時,不等待當前指令結束接着去處理下一個指令。

四、處理器指令重排篇

前面介紹了指令重排將會減少處理器的等待時間進而去處理其他的指令。這種一個指令還未結束便去執行其它指令的行為稱之為,指令重排。大白話就是指令未按需執行。

1,指令重排的實現

 

 

Store Buffere---存儲緩存

store buffer即存儲緩存。位於內核和緩存之間。當處理器需要處理將計算結果寫入在緩存中處於shared狀態的數據時,需要通知其他內核將該緩存置為 Invalid(無效),引入store buffer后將不再需要處理器去等待其他內核的響應結果,只需要把修改的數據寫到store buffer,通知其他內核,然后當前內核即可去執行其它指令。當收到其他內核的響應結果后,再把store buffer中的數據寫回緩存,並修改狀態為M。(很類似分布式中,數據一致性保障的異步確認)

Invalidate Queue---失效隊列

簡單說處理器修改數據時,需要通知其它內核將該緩存中的數據置為Invalid(失效),我們將該數據放到了Store Buffere處理。那收到失效指令的這些內核會立即處理這種失效消息嗎?答案是不會的,因為就算是一個內核緩存了該數據並不意味着馬上要用,這些內核會將失效通知放到Invalidate Queue,然后快速返回Invalidate Acknowledge消息(意思就是盡量不耽誤正在用這個數據的內核正常工作)。后續收到失效通知的內核將會從該queue中逐個處理該命令。(意思就是我也不着急用,所以我也不着急處理)。

2,指令重排帶來的問題---可見性問題

指令重排或者說store buffer或者invalidte queue帶來的問題就是可見性問題,通過以上分析,我們不難發現這其實是保障了數據的最終一致性。因為在處理器對數據的修改不是立即對其他內核可見的,因為修改了的數據被放在了store buffer中,通知其他內核的數據修改也不是達到其他內核並被立即處理的。其實有點異步處理的意思。
數據不一致性問題
假設core1對數據A的修改通知沒有被core2立即處理(因為在invalidte queue中),core2緊接着又修改了數據A,是不是就造成了數據的不一致。其它內核對數據的修改對本內核是不可見的。
為了提升性能進入了指令重排,而然指令重排可能會導致數據的不一致,這可如何是好?哈哈哈,當然是有解決方案的,答案就是內存屏障。更多精彩內容且聽下回分解。

五、內存屏障

1,內存屏障

上一章節中說到指令重排導致的可見性問題可能會導致數據的不一致。CPU就給我們提供了一直通過軟件告知CPU什么指令不能重排,什么指令能重排的機制就是內存屏障。
兩個指令:

load:將內存中的數據拷貝到內核的緩存中。
store:將內核緩存的數據刷新到內存中。

內存屏障:Memory Barrier。
內存屏障又分為四種:

LoadLoad Barriers(讀屏障),StoreStore Barriers(寫屏障),LoadStore Barriers,StoreLoad Barriers

不同的CPU架構對內存屏障的實現是不盡相同的,我們這里討論流行的X86架構。
X86中有三種內存屏障:
Store Memory Barrier:寫屏障,等同於前文的StoreStore Barriers
告訴處理器在執行這之后的指令之前,執行所有已經在存儲緩存(store buffer)中的修改(M)指令。即:所有store barrier之前的修改(M)指令都是對之后的指令可見。
Load Memory Barrier:讀屏障,等同於前文的LoadLoad Barriers
告訴處理器在執行任何的加載前,執行所有已經在失效隊列(Invalidte Queues)中的失效(I)指令。即:所有load barrier之前的store指令對之后(本核心和其他核心)的指令都是可見的。
Full Barrier:萬能屏障,即Full barrier作用等同於以上二者之和。
即所有store barrier之前的store指令對之后的指令都是可見的,之后(本核心和其他核心)的指令也都是可見的,完全保證了數據的強一致性。

2,內存屏障的問題

CPU知道什么時候需要加入內存屏障,什么時候不需要嗎?CPU將這個加入內存屏障的時機交給了程序員。在java中這個加入內存屏障的命令就是volatile關鍵字。
澄清一點,volatile並不是僅僅加入內存屏障這么簡單,加入內存屏障只是volatile內核指令級別的內存語義。
除此之外:volatile還可以禁止編譯器的指令重排,因為JVM為了優化性能並且不違反happens-before原則的前提下也會進行指令重排。

六、Volatile對可見性和有序性的保障

並發下的三個概念:

原子性(Atomicity):一個操作是不可中斷的,要么全部執行成功要么全部執行失敗。
可見性(Visibility):所有線程都能看到共享內存的最新狀態。
有序性(Ordering):即程序執行的順序按照代碼的先后順序執行。

Volatile是JMM(Java Memory Model:java內存模型)中對可見性和有序性的保障。
Volatile內存語義:

可見性:可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
有序性:對一個volatile域的寫,happens-before於任意后續對這個volatile域的讀。其實就是禁止指令重排。

說了這么多終於說到了Volatile的有序性,Volatile正是通過對處理器加入內存屏障來禁止指令重排從來保證有序性的。大白話來說Volatile就是程序員決定加入內存屏障的指令。
當然在java語言中,指令重排並不是只發生在處理器層面。在java編譯器中JVM也會存在指令重排優化,以提高程序的性能。Volatile同樣可以禁止編譯器層面的指令重排。
下面這段話摘自《深入理解Java虛擬機》:
  “觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的匯編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令”
  lock前綴指令實際上相當於前文介紹的插入內存屏障指令。(這個lock指令是JMM中的,再往底層就是加入內存屏障指令)
關於JMM,happens-before原則及volatile的其它內容后面打算再寫一篇介紹。

七、總結

  1. CPU與三級緩存:為了解決CPU按照摩爾定律提升的計算能力和內存緩慢發展的不平衡,三級緩存以其比內存更加強悍的讀寫能力,在CPU和內存中間充當了一層緩存,緩解了這種發展不平衡帶來的矛盾。
  2. 三級緩存與MESI協議:CPU內核(流行的架構例如X86)有各自的一級緩存或者二級緩存,而三級緩存和主內存是多核共享,內核之間的數據共享和數據獨占將導致緩存一致性問題(一句話就是數據共享導致的數據一致性問題),從而有了MESI CPU緩存一致性協議來保證數據的一致性問題。
  3. MESI協議與強一致性:MESI協議本質上是多核操作共享數據的串行化強一致性保障,這種串行將導致CPU的資源浪費。
  4. 指令重排優化與最終一致性:在多核間Invalidate消息的同步響應浪費了CPU一定的計算資源,從而有了指令重排。在內核處理器中對數據的修改不直接寫到緩存中,而是先放入store buffer,然后向其他核心發送Invalidate消息的期間,本內核可以繼續執行其他指令。而收到Invalidate消息的內核將Invalidate消息放到Invalidate Queue中延時處理。當本內核收到其他內核關於Invalidate的invalidate acknowledge響應才會將store buffer中的對應指令刷新到緩存中。這樣store buffer和Invalidate Queue實現的指令重排就通過異步確認和延時消費(這是分布式的概念,但是我覺在用在這里很合理)保證了數據的最終一致性。
  5. 內存屏障與強一致性:如上所說指令重排是采用的是異步確認和延時消費保障了數據的最終一致性,但是在有些場景下這種機制也將導致數據的不一致,所以在一些特殊場景下我們需要禁止指令重排從而保證數據的強一致性。禁止指令重排的機制就是對CPU加入內存屏障,從而禁止一些會引起錯誤的指令重排。
  6. 在CPU角度,他是無法判斷什么時候需要加入內存屏障,什么時候不需要(假如內核自己去判斷,相信這個判斷將會比保障強一致性更浪費資源,那指令重排就畫蛇添足了),所以這個加入內存屏障的活就拋給了上層應用也就是軟件層。在Java語言中,如果完全將加入內存屏障的活交給程序員,相信Java程序員會非常痛苦。所以JMM的一些規定比如happens-before和一些可見性和有序性的規則,將會減少程序員的壓力,因為在一些JVM可預測的場景中,JMM規范會自動實現這種內存屏障。而程序員需要關心的就是在一些高並發場景下我們可以使用Volatile關鍵字去告訴JVM和CPU我要加入內存屏障。當然這不是Volatile存在的所有意義。

最后想說一句,想徹底理解Volatile不了解以上那些知識是不夠的。
感謝屏幕前的你能看到最后。

右下角推薦,謝謝。

 

  如有錯誤的地方還請留言指正。
  原創不易,轉載請注明原文地址:https://www.cnblogs.com/hello-shf/p/12091591.html

 

  參考文獻:
  https://blog.51cto.com/14220760/2370118?source=dra

  https://www.zhihu.com/question/296949412

  https://www.open-open.com/lib/view/open1426815960648.html


免責聲明!

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



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