久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

關(guān)于volatile、MESI,、內(nèi)存屏障,、#Lock

 liang1234_ 2020-09-07

最近又看了下Disruptor,里面提到了內(nèi)存屏障,,突然想到了指令重排,、還有可見(jiàn)性,感覺(jué)里面關(guān)系有點(diǎn)亂,,就翻了下,,因此就寫(xiě)了這篇文章

帶著幾個(gè)問(wèn)題:

  • 1.volatile,,是怎么可見(jiàn)性的問(wèn)題(CPU緩存),那么他是怎么解決的--->MESI

  • 2.CAS指令,,確保了對(duì)同一個(gè)同一個(gè)內(nèi)存地址操作的原子性,,那么他應(yīng)該也會(huì)遇到和上面可見(jiàn)性一樣的問(wèn)題,他是怎么解決的,,是不是和volatile的底層原理類似,?--->是的,也是利用了MESI

  • 3.volatile還避免了指令重排,,是通過(guò)內(nèi)存屏障解決的,?那么他和MESI有什么關(guān)系?還是說(shuō)volatile關(guān)鍵字即用了MESI也用了內(nèi)存屏障,?--->是的,,其實(shí)MESI底層也還是需要內(nèi)存屏障

一、可見(jiàn)性和MESI

1.1 可見(jiàn)性

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

現(xiàn)代處理器的緩存一般分為三級(jí),,由每一個(gè)核心獨(dú)享的L1、L2 Cache,,以及所有的核心共享L3 Cache組成,,具體每個(gè)cache,實(shí)際上是有很多緩存行組成:


1.2 緩存一致性和MESI

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

  • 獨(dú)占(exclusive):僅當(dāng)前處理器擁有該緩存行,并且沒(méi)有修改過(guò),,是最新的值,。

  • 共享(share):有多個(gè)處理器擁有該緩存行,每個(gè)處理器都沒(méi)有修改過(guò)緩存,,是最新的值,。

  • 修改(modified):僅當(dāng)前處理器擁有該緩存行,并且緩存行被修改過(guò)了,,一定時(shí)間內(nèi)會(huì)寫(xiě)回主存,,會(huì)寫(xiě)成功狀態(tài)會(huì)變?yōu)镾,。

  • 失效(invalid):緩存行被其他處理器修改過(guò),該值不是最新的值,,需要讀取主存上最新的值,。

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

  • 一個(gè)處于M狀態(tài)的緩存行,必須時(shí)刻監(jiān)聽(tīng)所有試圖讀取該緩存行對(duì)應(yīng)的主存地址的操作,,如果監(jiān)聽(tīng)到,,則必須在此操作執(zhí)行前把其緩存行中的數(shù)據(jù)寫(xiě)回CPU。

  • 一個(gè)處于S狀態(tài)的緩存行,,必須時(shí)刻監(jiān)聽(tīng)使該緩存行無(wú)效或者獨(dú)享該緩存行的請(qǐng)求,,如果監(jiān)聽(tīng)到,則必須把其緩存行狀態(tài)設(shè)置為I,。

  • 一個(gè)處于E狀態(tài)的緩存行,,必須時(shí)刻監(jiān)聽(tīng)其他試圖讀取該緩存行對(duì)應(yīng)的主存地址的操作,如果監(jiān)聽(tīng)到,,則必須把其緩存行狀態(tài)設(shè)置為S,。

  • 當(dāng)CPU需要讀取數(shù)據(jù)時(shí),如果其緩存行的狀態(tài)是I的,,則需要從內(nèi)存中讀取,,并把自己狀態(tài)變成S,如果不是I,,則可以直接讀取緩存中的值,,但在此之前,必須要等待其他CPU的監(jiān)聽(tīng)結(jié)果,,如其他CPU也有該數(shù)據(jù)的緩存且狀態(tài)是M,,則需要等待其把緩存更新到內(nèi)存之后,再讀取,。

  • 當(dāng)CPU需要寫(xiě)數(shù)據(jù)時(shí),,只有在其緩存行是M或者E的時(shí)候才能執(zhí)行,否則需要發(fā)出特殊的RFO指令(Read Or Ownership,,這是一種總線事務(wù)),,通知其他CPU置緩存無(wú)效(I),這種情況下會(huì)性能開(kāi)銷是相對(duì)較大的,。在寫(xiě)入完成后,,修改其緩存狀態(tài)為M。

這個(gè)圖的含義就是當(dāng)一個(gè)core持有一個(gè)cacheline的狀態(tài)為Y時(shí),其它c(diǎn)ore對(duì)應(yīng)的cacheline應(yīng)該處于狀態(tài)X, 比如地址 0x00010000 對(duì)應(yīng)的cacheline在core0上為狀態(tài)M, 則其它所有的core對(duì)應(yīng)于0x00010000的cacheline都必須為I , 0x00010000 對(duì)應(yīng)的cacheline在core0上為狀態(tài)S, 則其它所有的core對(duì)應(yīng)于0x00010000的cacheline 可以是S或者I ,

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

存儲(chǔ)緩存(Store Buffe)

也就是常說(shuō)的寫(xiě)緩存,,當(dāng)處理器修改緩存時(shí),,把新值放到存儲(chǔ)緩存中,處理器就可以去干別的事了,,把剩下的事交給存儲(chǔ)緩存,。

失效隊(duì)列(Invalidate Queues)

處理失效的緩存也不是簡(jiǎn)單的,需要讀取主存,。并且存儲(chǔ)緩存也不是無(wú)限大的,,那么當(dāng)存儲(chǔ)緩存滿的時(shí)候,處理器還是要等待失效響應(yīng)的,。為了解決上面兩個(gè)問(wèn)題,,引進(jìn)了失效隊(duì)列(invalidate queue)。處理失效的工作如下:

  • 收到失效消息時(shí),,放到失效隊(duì)列中去,。

  • 為了不讓處理器久等失效響應(yīng),收到失效消息需要馬上回復(fù)失效響應(yīng),。

  • 為了不頻繁阻塞處理器,,不會(huì)馬上讀主存以及設(shè)置緩存為invlid,合適的時(shí)候再一塊處理失效隊(duì)列,。

1.3 MESI和CAS關(guān)系

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

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

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

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

2.1 指令重排

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

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

上面這段代碼,,如果操作1如果發(fā)生cache miss,則需要等待讀取內(nèi)存外存,??纯从袥](méi)有能優(yōu)先執(zhí)行的指令,操作2依賴于操作1,,不能被優(yōu)先執(zhí)行,操作3不依賴1和2,,所以能優(yōu)先執(zhí)行操作3,。
JVM的JSR-133規(guī)范中定義了as-if-serial語(yǔ)義,即compiler, runtime, and hardware三者需要保證在單線程模型下程序不會(huì)感知到指令重排的影響,。

在并發(fā)模型下,,重排序還是可能會(huì)引發(fā)問(wèn)題,,比較經(jīng)典的就是“單例模式失效”問(wèn)題(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;
   }
}

上面這段代碼,初看沒(méi)問(wèn)題,,但是在并發(fā)模型下,,可能會(huì)出錯(cuò),那是因?yàn)閕nstance= new Singleton()并非一個(gè)原子操作,它實(shí)際上下面這三個(gè)操作:

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

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

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

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

在java里面volatile可以防止重排,當(dāng)然還有另外一個(gè)作用即內(nèi)存可見(jiàn)性,,這個(gè)知道的人還應(yīng)該比較普遍,,就不說(shuō)了

2.2 內(nèi)存屏障

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

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

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

  • LoadLoad屏障:(指令Load1; LoadLoad; Load2),,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問(wèn)前,,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

  • LoadStore屏障:(指令Load1; LoadStore; Store2),,在Store2及后續(xù)寫(xiě)入操作被刷出前,,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

  • StoreStore屏障:(指令Store1; StoreStore; Store2),,在Store2及后續(xù)寫(xiě)入操作執(zhí)行前,,保證Store1的寫(xiě)入操作對(duì)其它處理器可見(jiàn)。

  • StoreLoad屏障:(指令Store1; StoreLoad; Load2),,在Load2及后續(xù)所有讀取操作執(zhí)行前,,保證Store1的寫(xiě)入對(duì)所有處理器可見(jiàn)。它的開(kāi)銷是四種屏障中最大的,。在大多數(shù)處理器的實(shí)現(xiàn)中,,這個(gè)屏障是個(gè)萬(wàn)能屏障,兼具其它三種內(nèi)存屏障的功能

對(duì)于volatile關(guān)鍵字,,按照規(guī)范會(huì)有下面的操作:

  • 在每個(gè)volatile寫(xiě)入之前,,插入一個(gè)StoreStore,,寫(xiě)入之后,插入一個(gè)StoreLoad

  • 在每個(gè)volatile讀取之前,,插入LoadLoad,,之后插入LoadStore

具體到X86來(lái)看,其實(shí)沒(méi)那么多指令,,只有StoreLoad:


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

三,、happends-before

結(jié)合前面的兩點(diǎn),再看happends-before就比較好理解了,。因?yàn)楣庹f(shuō)可見(jiàn)性和重排很難聯(lián)想到happends-before,。這個(gè)點(diǎn)在并發(fā)編程里還是非常重要的,再詳細(xì)記錄下:

  • 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

四,、實(shí)現(xiàn) --> #lock

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

參考

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多