三級緩存,MESI緩存一致性協議,指令重排,內存屏障,JMM,volatile。單拿一個出來,想必你們對這些概念應該有必定了解。可是這些東西有什麼必然的聯繫,或者他們之間究竟有什麼前世此生想必是困擾你們的一個問題。
爲何有了MESI協議,咱們還須要volatile?
內存屏障的由來?
指令重排帶來的問題?
下面咱們經過分析每個技術的由來,以及帶來的負面影響,跟你們探討一下這些技術之間的聯繫。具體每一個關鍵詞相關文章也不少再也不贅述,只談我的理解。html
CPU的發展很快,幾乎18-24個月,CPU的計算能力就能翻一番,然而內存的發展卻相對緩慢,以致於內存的讀寫速度遠遠跟不上CPU的計算能力,也就是說,在CPU和內存的資源交換中,CPU經常須要等待內存,而浪費了大量的計算能力。三級緩存的出現正是爲了彌補內存的慢和CPU的快而誕生的產物。
CPU將再也不直接與內存進行數據的交換而是在兩者之間加入一個緩存以解決這種不協調而致使的資源浪費狀況。固然了緩存必定是比內存的讀寫速度更快的,否則也沒有這個必要了。那是否是咱們直接用緩存就不用內存了,呵呵,錢錢錢。
所謂的三級緩存正是誕生在這樣的一個背景下。
三級緩存分爲三種:L1 Cache,L2 Cache,L3 Cachejava
內存以及三級緩存的響應時間如上所示,由此可知內存和緩存的差距仍是很大的,當時硬件價格和上圖響應時間是成正比的。響應時間越快,價格越貴。程序員
不一樣的CPU廠商的架構也有些不一樣,在這裏只介紹流行的緩存架構緩存
緩存的意義架構
1 時間侷限性:若是某個數據被訪問,那麼它在不久的未來有可能被在次訪問 2 空間侷限性:若是某個緩存行的數據被訪問,那麼與之相鄰的緩存行很快可能被訪問到
緩存的加入是爲了解決CPU運算能力和內存讀寫能力的不匹配問題,簡單來講就是爲了提高資源利用率。那麼在多CPU(一個CPU對應一個或者多個核心)或者多核心下,每一個核心都會有一個一級緩存或者二級緩存,也就是說一二級緩存是核心獨佔的(相似JMM模型,線程的工做內存是線程獨佔的,主內存是共享的)而三級緩存和主內存是共享的,這樣就將致使CPU緩存一致性問題。爲了解決這種不一致,大名鼎鼎的MESI協議隨之而來。併發
MESI協議的由來上個章節已經介紹,在此不作過多介紹app
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的計算能力。
多個core的緩存狀態置換是須要消耗時間的,致使內核在此期間將無事可作。甚至一旦某一個內核發生阻塞,將會致使其餘內核也處於阻塞,從而帶來性能和穩定性的極大消耗。
因此這時指令重排開始發揮它的價值。想一想這種等待有時是沒有必要的,由於在這個等待時間內內核徹底能夠去幹一些其餘事情。即當內核處於等待狀態時,不等待當前指令結束接着去處理下一個指令。
前面介紹了指令重排將會減小處理器的等待時間進而去處理其餘的指令。這種一個指令還未結束便去執行其它指令的行爲稱之爲,指令重排。大白話就是指令未按需執行。
Store Buffere---存儲緩存
store buffer即存儲緩存。位於內核和緩存之間。當處理器須要處理將計算結果寫入在緩存中處於shared狀態的數據時,須要通知其餘內核將該緩存置爲 Invalid(無效),引入store buffer後將再也不須要處理器去等待其餘內核的響應結果,只須要把修改的數據寫到store buffer,通知其餘內核,而後當前內核便可去執行其它指令。當收到其餘內核的響應結果後,再把store buffer中的數據寫回緩存,並修改狀態爲M。(很相似分佈式中,數據一致性保障的異步確認)
Invalidate Queue---失效隊列
簡單說處理器修改數據時,須要通知其它內核將該緩存中的數據置爲Invalid(失效),咱們將該數據放到了Store Buffere處理。那收到失效指令的這些內核會當即處理這種失效消息嗎?答案是不會的,由於就算是一個內核緩存了該數據並不意味着立刻要用,這些內核會將失效通知放到Invalidate Queue,而後快速返回Invalidate Acknowledge消息(意思就是儘可能不耽誤正在用這個數據的內核正常工做)。後續收到失效通知的內核將會從該queue中逐個處理該命令。(意思就是我也不着急用,因此我也不着急處理)。
指令重排或者說store buffer或者invalidte queue帶來的問題就是可見性問題,經過以上分析,咱們不難發現這實際上是保障了數據的最終一致性。由於在處理器對數據的修改不是當即對其餘內核可見的,由於修改了的數據被放在了store buffer中,通知其餘內核的數據修改也不是達到其餘內核並被當即處理的。其實有點異步處理的意思。
數據不一致性問題
假設core1對數據A的修改通知沒有被core2當即處理(由於在invalidte queue中),core2緊接着又修改了數據A,是否是就形成了數據的不一致。其它內核對數據的修改對本內核是不可見的。
爲了提高性能進入了指令重排,而然指令重排可能會致使數據的不一致,這可如何是好?哈哈哈,固然是有解決方案的,答案就是內存屏障。更多精彩內容且聽下回分解。
上一章節中說到指令重排致使的可見性問題可能會致使數據的不一致。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指令對以後的指令都是可見的,以後(本核心和其餘核心)的指令也都是可見的,徹底保證了數據的強一致性。
CPU知道何時須要加入內存屏障,何時不須要嗎?CPU將這個加入內存屏障的時機交給了程序員。在java中這個加入內存屏障的命令就是volatile關鍵字。
澄清一點,volatile並非僅僅加入內存屏障這麼簡單,加入內存屏障只是volatile內核指令級別的內存語義。
除此以外:volatile還能夠禁止編譯器的指令重排,由於JVM爲了優化性能而且不違反happens-before原則的前提下也會進行指令重排。
併發下的三個概念:
原子性(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的其它內容後面打算再寫一篇介紹。
最後想說一句,想完全理解Volatile不瞭解以上那些知識是不夠的。
感謝屏幕前的你能看到最後。
右下角推薦,謝謝。
若有錯誤的地方還請留言指正。
原創不易,轉載請註明原文地址:http://www.javashuo.com/article/p-nqjwgkuj-nu.html
參考文獻:
https://blog.51cto.com/14220760/2370118?source=dra
https://www.zhihu.com/question/296949412