關于volatile慨飘、MESI、內(nèi)存屏障洞渔、#Lock

最近又看了下Disruptor套媚,里面提到了內(nèi)存屏障缚态,突然想到了指令重排、還有可見性堤瘤,感覺里面關系有點亂玫芦,就翻了下,因此就寫了這篇文章

帶著幾個問題:

  • 1.volatile本辐,是怎么可見性的問題(CPU緩存)桥帆,那么他是怎么解決的--->MESI
  • 2.CAS指令,確保了對同一個同一個內(nèi)存地址操作的原子性慎皱,那么他應該也會遇到和上面可見性一樣的問題老虫,他是怎么解決的,是不是和volatile的底層原理類似茫多?--->是的祈匙,也是利用了MESI
  • 3.volatile還避免了指令重排,是通過內(nèi)存屏障解決的天揖?那么他和MESI有什么關系夺欲?還是說volatile關鍵字即用了MESI也用了內(nèi)存屏障?--->是的今膊,其實MESI底層也還是需要內(nèi)存屏障

一些阅、可見性和MESI

1.1 可見性

在JVM的內(nèi)存模型中,每個線程有自己的工作內(nèi)存斑唬,實際上JAVA線程借助了底層操作系統(tǒng)線程實現(xiàn)市埋,一個JVM線程對應一個操作系統(tǒng)線程,線程的工作內(nèi)存其實是cpu寄存器和高速緩存的抽象

現(xiàn)代處理器的緩存一般分為三級恕刘,由每一個核心獨享的L1缤谎、L2 Cache,以及所有的核心共享L3 Cache組成雪营,具體每個cache弓千,實際上是有很多緩存行組成:


1.2 緩存一致性和MESI

緩存一致性協(xié)議給緩存行(通常為64字節(jié))定義了個狀態(tài):獨占(exclusive)、共享(share)献起、修改(modified)洋访、失效(invalid),用來描述該緩存行是否被多處理器共享谴餐、是否修改姻政。所以緩存一致性協(xié)議也稱MESI協(xié)議。

  • 獨占(exclusive):僅當前處理器擁有該緩存行岂嗓,并且沒有修改過汁展,是最新的值。
  • 共享(share):有多個處理器擁有該緩存行,每個處理器都沒有修改過緩存食绿,是最新的值侈咕。
  • 修改(modified):僅當前處理器擁有該緩存行,并且緩存行被修改過了器紧,一定時間內(nèi)會寫回主存耀销,會寫成功狀態(tài)會變?yōu)镾。
  • 失效(invalid):緩存行被其他處理器修改過铲汪,該值不是最新的值熊尉,需要讀取主存上最新的值。

協(xié)議協(xié)作如下:

  • 一個處于M狀態(tài)的緩存行掌腰,必須時刻監(jiān)聽所有試圖讀取該緩存行對應的主存地址的操作狰住,如果監(jiān)聽到,則必須在此操作執(zhí)行前把其緩存行中的數(shù)據(jù)寫回CPU齿梁。
  • 一個處于S狀態(tài)的緩存行催植,必須時刻監(jiān)聽使該緩存行無效或者獨享該緩存行的請求,如果監(jiān)聽到士飒,則必須把其緩存行狀態(tài)設置為I查邢。
  • 一個處于E狀態(tài)的緩存行,必須時刻監(jiān)聽其他試圖讀取該緩存行對應的主存地址的操作酵幕,如果監(jiān)聽到,則必須把其緩存行狀態(tài)設置為S缓苛。
  • 當CPU需要讀取數(shù)據(jù)時芳撒,如果其緩存行的狀態(tài)是I的,則需要從內(nèi)存中讀取未桥,并把自己狀態(tài)變成S笔刹,如果不是I,則可以直接讀取緩存中的值冬耿,但在此之前舌菜,必須要等待其他CPU的監(jiān)聽結(jié)果,如其他CPU也有該數(shù)據(jù)的緩存且狀態(tài)是M亦镶,則需要等待其把緩存更新到內(nèi)存之后日月,再讀取。
  • 當CPU需要寫數(shù)據(jù)時缤骨,只有在其緩存行是M或者E的時候才能執(zhí)行爱咬,否則需要發(fā)出特殊的RFO指令(Read Or Ownership,這是一種總線事務)绊起,通知其他CPU置緩存無效(I)精拟,這種情況下會性能開銷是相對較大的。在寫入完成后,修改其緩存狀態(tài)為M蜂绎。

這個圖的含義就是當一個core持有一個cacheline的狀態(tài)為Y時,其它core對應的cacheline應該處于狀態(tài)X, 比如地址 0x00010000 對應的cacheline在core0上為狀態(tài)M, 則其它所有的core對應于0x00010000的cacheline都必須為I , 0x00010000 對應的cacheline在core0上為狀態(tài)S, 則其它所有的core對應于0x00010000的cacheline 可以是S或者I ,

另外MESI協(xié)議為了提高性能栅表,引入了Store Buffe和Invalidate Queues,還是有可能會引起緩存不一致师枣,還會再引入內(nèi)存屏障來確保一致性谨读,可以參考[7]和[12]

存儲緩存(Store Buffe)

也就是常說的寫緩存,當處理器修改緩存時坛吁,把新值放到存儲緩存中劳殖,處理器就可以去干別的事了,把剩下的事交給存儲緩存拨脉。

失效隊列(Invalidate Queues)

處理失效的緩存也不是簡單的哆姻,需要讀取主存。并且存儲緩存也不是無限大的玫膀,那么當存儲緩存滿的時候矛缨,處理器還是要等待失效響應的。為了解決上面兩個問題帖旨,引進了失效隊列(invalidate queue)箕昭。處理失效的工作如下:

  • 收到失效消息時,放到失效隊列中去解阅。
  • 為了不讓處理器久等失效響應落竹,收到失效消息需要馬上回復失效響應。
  • 為了不頻繁阻塞處理器货抄,不會馬上讀主存以及設置緩存為invlid述召,合適的時候再一塊處理失效隊列。

1.3 MESI和CAS關系

在x86架構(gòu)上蟹地,CAS被翻譯為”lock cmpxchg...“积暖,當兩個core同時執(zhí)行針對同一地址的CAS指令時,其實他們是在試圖修改每個core自己持有的Cache line,

假設兩個core都持有相同地址對應cacheline,且各自cacheline 狀態(tài)為S, 這時如果要想成功修改,就首先需要把S轉(zhuǎn)為E或者M, 則需要向其它core invalidate 這個地址的cacheline,則兩個core都會向ring bus發(fā)出 invalidate這個操作, 那么在ringbus上就會根據(jù)特定的設計協(xié)議仲裁是core0,還是core1能贏得這個invalidate, 勝者完成操作, 失敗者需要接受結(jié)果, invalidate自己對應的cacheline,再讀取勝者修改后的值, 回到起點.

對于我們的CAS操作來說, 其實鎖并沒有消失,只是轉(zhuǎn)嫁到了ring bus的總線仲裁協(xié)議中. 而且大量的多核同時針對一個地址的CAS操作會引起反復的互相invalidate 同一cacheline, 造成pingpong效應, 同樣會降低性能(參考[9])。當然如果真的有性能問題怪与,我覺得這可能會在ns級別體現(xiàn)了,一般的應用程序中使用CAS應該不會引起性能問題

二夺刑、指令重排和內(nèi)存屏障

2.1 指令重排

現(xiàn)代CPU的速度越來越快,為了充分的利用CPU分别,在編譯器和CPU執(zhí)行期遍愿,都可能對指令重排。舉個例子:

LDR R1, [R0];//操作1
ADD R2, R1, R1;//操作2
ADD R3, R4, R4;//操作3

上面這段代碼茎杂,如果操作1如果發(fā)生cache miss错览,則需要等待讀取內(nèi)存外存』屯看看有沒有能優(yōu)先執(zhí)行的指令倾哺,操作2依賴于操作1轧邪,不能被優(yōu)先執(zhí)行,操作3不依賴1和2羞海,所以能優(yōu)先執(zhí)行操作3忌愚。
JVM的JSR-133規(guī)范中定義了as-if-serial語義,即compiler, runtime, and hardware三者需要保證在單線程模型下程序不會感知到指令重排的影響却邓。

在并發(fā)模型下硕糊,重排序還是可能會引發(fā)問題,比較經(jīng)典的就是“單例模式失效”問題(DoubleCheckedLocking):

public class Singleton {
  private static Singleton instance = null;

  private Singleton() { }

  public static Singleton getInstance() {
     if(instance == null) {
        synchronzied(Singleton.class) {
           if(instance == null) {
               instance = new Singleton();  //
           }
        }
     }
     return instance;
   }
}

上面這段代碼腊徙,初看沒問題简十,但是在并發(fā)模型下,可能會出錯,那是因為instance= new Singleton()并非一個原子操作撬腾,它實際上下面這三個操作:

memory =allocate();    //1:分配對象的內(nèi)存空間
ctorInstance(memory);  //2:初始化對象
instance =memory;     //3:設置instance指向剛分配的內(nèi)存地址

上面操作2依賴于操作1螟蝙,但是操作3并不依賴于操作2,所以JVM是可以針對它們進行指令的優(yōu)化重排序的民傻,經(jīng)過重排序后如下:

memory =allocate();    //1:分配對象的內(nèi)存空間
instance =memory;     //3:instance指向剛分配的內(nèi)存地址胰默,此時對象還未初始化
ctorInstance(memory);  //2:初始化對象

可以看到指令重排之后,instance指向分配好的內(nèi)存放在了前面漓踢,而這段內(nèi)存的初始化被排在了后面牵署。在多線程場景下,可能A線程執(zhí)行到了3喧半,B線程發(fā)現(xiàn)已經(jīng)不為空就返回繼續(xù)執(zhí)行验烧,就會出錯寨辩。

在java里面volatile可以防止重排划址,當然還有另外一個作用即內(nèi)存可見性实抡,這個知道的人還應該比較普遍兴垦,就不說了

2.2 內(nèi)存屏障

硬件層的內(nèi)存屏障分為兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障如暖。內(nèi)存屏障有兩個作用:

1.阻止屏障兩側(cè)的指令重排序率挣;
2.強制把寫緩沖區(qū)/高速緩存中的臟數(shù)據(jù)等寫回主內(nèi)存清寇,讓緩存中相應的數(shù)據(jù)失效浩村。

在JSR規(guī)范中定義了4種內(nèi)存屏障:

  • LoadLoad屏障:(指令Load1; LoadLoad; Load2)做葵,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前,保證Load1要讀取的數(shù)據(jù)被讀取完畢心墅。
  • LoadStore屏障:(指令Load1; LoadStore; Store2)酿矢,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢怎燥。
  • StoreStore屏障:(指令Store1; StoreStore; Store2)瘫筐,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見铐姚。
  • StoreLoad屏障:(指令Store1; StoreLoad; Load2)策肝,在Load2及后續(xù)所有讀取操作執(zhí)行前肛捍,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的之众。在大多數(shù)處理器的實現(xiàn)中拙毫,這個屏障是個萬能屏障,兼具其它三種內(nèi)存屏障的功能

對于volatile關鍵字棺禾,按照規(guī)范會有下面的操作:

  • 在每個volatile寫入之前缀蹄,插入一個StoreStore,寫入之后膘婶,插入一個StoreLoad
  • 在每個volatile讀取之前缺前,插入LoadLoad,之后插入LoadStore

具體到X86來看悬襟,其實沒那么多指令衅码,只有StoreLoad:


結(jié)合上面的【一】和【二】的內(nèi)容,內(nèi)存屏障首先阻止了指令的重排古胆,另外也和MESI協(xié)議結(jié)合肆良,確保了內(nèi)存的可見性

三、happends-before

結(jié)合前面的兩點逸绎,再看happends-before就比較好理解了惹恃。因為光說可見性和重排很難聯(lián)想到happends-before。這個點在并發(fā)編程里還是非常重要的棺牧,再詳細記錄下:

  • 1.Each action in a thread happens-before every subsequent action in that thread
  • 2.An unlock on a monitor happens-before every subsequent lock on that monitor.
  • 3.A write to a volatile field happens-before every subsequent read of that volatile
  • 4.A call to start() on a thread happens-before any actions in the started thread.
  • 5.All actions in a thread happen-before any other thread successfully returns from a join() on
    that thread.
  • 6.If an action a happens-before an action b, and b happens before an action c, then a happensbefore c

四巫糙、實現(xiàn) --> #lock

再往下挖一層,會發(fā)現(xiàn)volatile關鍵字颊乘,轉(zhuǎn)換成指令以后参淹,會有一個#lock前綴...原來以為會有相應的內(nèi)存屏障指令,說好的內(nèi)存屏障的那些呢乏悄?
后來參考了資料[11]以及其他一些文章以后才了解到浙值,任何帶有l(wèi)ock前綴的指令以及CPUID等指令都有內(nèi)存屏障的作用。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末檩小,一起剝皮案震驚了整個濱河市开呐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌规求,老刑警劉巖筐付,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阻肿,居然都是意外死亡瓦戚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門丛塌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來较解,“玉大人畜疾,你說我怎么就攤上這事∩谄海” “怎么了庸疾?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長当编。 經(jīng)常有香客問我届慈,道長,這世上最難降的妖魔是什么忿偷? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任金顿,我火速辦了婚禮,結(jié)果婚禮上鲤桥,老公的妹妹穿的比我還像新娘揍拆。我一直安慰自己,他們只是感情好茶凳,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布嫂拴。 她就那樣靜靜地躺著,像睡著了一般贮喧。 火紅的嫁衣襯著肌膚如雪筒狠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天箱沦,我揣著相機與錄音辩恼,去河邊找鬼。 笑死谓形,一個胖子當著我的面吹牛灶伊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寒跳,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼聘萨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了童太?” 一聲冷哼從身側(cè)響起匈挖,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎康愤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舶吗,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡征冷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了誓琼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片检激。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡肴捉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叔收,到底是詐尸還是另有隱情齿穗,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布饺律,位于F島的核電站窃页,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏复濒。R本人自食惡果不足惜脖卖,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望巧颈。 院中可真熱鬧畦木,春花似錦、人聲如沸砸泛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唇礁。三九已至勾栗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間垒迂,已是汗流浹背械姻。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留机断,地道東北人楷拳。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像吏奸,于是被迫代替她去往敵國和親欢揖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容