1. JUC并發(fā)編程之MESI緩存一致協(xié)議詳解

        共 3088字,需瀏覽 7分鐘

         ·

        2021-05-14 16:46

        點(diǎn)擊上方藍(lán)字 關(guān)注我吧



        1
        前言

        經(jīng)過幾篇文章,我一直在講到并發(fā)下可能會(huì)導(dǎo)致很多問題的發(fā)生,通過volatile又能解決它的可見性和指令重排問題,在閱讀我的文章的時(shí)候,不知道大家伙是否好奇過在計(jì)算機(jī)底層,它是如何保證數(shù)據(jù)的安全性的,volatile為什么能夠解決這些問題?那么該文章就來用為簡(jiǎn)潔的話語來解釋MESI協(xié)議。


        2
        為什么需要緩存一致性協(xié)議


        在前面JMM內(nèi)存模型文章中,我有寫到多個(gè)線程并發(fā)訪問一個(gè)主內(nèi)存的共享變量時(shí),這些線程會(huì)在各自的工作內(nèi)存中拷貝一份共享變量的副本,那么這就帶來了一個(gè)問題,一個(gè)線程對(duì)共享變量進(jìn)行修改后,其他的線程該如何感知到共享變量的改變從而做出適當(dāng)?shù)姆磻?yīng),確保后續(xù)線程讀取這個(gè)共享變量的時(shí)候,總是最新的值,從而防止出現(xiàn)臟數(shù)據(jù)的情況。說到這,就不得不說一下緩存一致性的由來。


        3
        緩存一致性的由來


        首先我這里放上一張CPU內(nèi)核簡(jiǎn)易結(jié)構(gòu)圖


        在最初的CPU都是單核心,然而在CPU的高速發(fā)展下,有那么一段話,CPU發(fā)展的速度有一個(gè)摩爾定律,幾乎每過18個(gè)月就會(huì)更新一次,而內(nèi)存就沒有這樣的定律,它每次發(fā)展就像擠牙膏一樣,每次擠出一點(diǎn),所以CPU更新迭代快它的處理速度就很快,而內(nèi)存更新迭代慢所以處理的速度遠(yuǎn)遠(yuǎn)跟不上CPU處理的速度,且CPU每次從內(nèi)存中拿取數(shù)據(jù),需要通過系統(tǒng)總線拿取數(shù)據(jù),這樣就會(huì)導(dǎo)致性能嚴(yán)重的降低,為解決CPU與內(nèi)存之間速率不匹配的問題,現(xiàn)代計(jì)算機(jī)系統(tǒng)中引入了緩存(Cache)用于提高性能,這樣CPU就可以直接讀取緩存中的數(shù)據(jù)。


        以上圖為例,在多核CPU中,每個(gè)內(nèi)核都有自己的緩存,這就引來的一個(gè)問題,當(dāng)緩存的數(shù)據(jù)與內(nèi)存中的數(shù)據(jù)發(fā)生不一致的話該怎么辦?于是就引來了緩存一致性協(xié)議啦。


        3
        緩存一致性是什么


        MESI(Modified-Exclusive-Shared-Invalid)協(xié)議是一種廣為使用的緩存一致性協(xié)議,類似讀寫鎖 對(duì)于同一地址的讀內(nèi)存操作是并發(fā)的,針對(duì)同一地址的寫操作是獨(dú)占的,對(duì)于內(nèi)存地址寫操作同一時(shí)間只能由一個(gè)處理器來執(zhí)行。為了保持?jǐn)?shù)據(jù)的一致性,MESI將緩存條目的狀態(tài)劃分為Modified.Exclusive,Shared,Invalid


        MESI協(xié)議中一個(gè)緩存條目的狀態(tài)Flag值分為以下四種

        1. Invalid(無效的,記為I) 相應(yīng)緩存行中不包含任何內(nèi)存地址對(duì)應(yīng)的有效副本數(shù)據(jù),是緩存條目的初始狀態(tài)
        2. Shared(共享的,記為S)緩存行中包含相應(yīng)內(nèi)存地址數(shù)據(jù)的副本,其他處理器高速緩存中也可能包含相應(yīng)地址內(nèi)存的副本,緩存行中的數(shù)據(jù)與內(nèi)存的一致
        3. Exclusive(獨(dú)占的,記為E)緩存行獨(dú)占相應(yīng)內(nèi)存地址數(shù)據(jù)的副本,其他處理器高速緩存不包含相同的副本或者副本失效,緩存行中的數(shù)據(jù)與主內(nèi)存數(shù)據(jù)一致
        4. Modified(更改過,記為M)相應(yīng)緩存行包含更新后的數(shù)據(jù),其他處理器相同tag的緩存行只有唯一的M狀態(tài),與主內(nèi)存的數(shù)據(jù)不一致

        MESI定義了一組message用于協(xié)調(diào)各個(gè)處理器的讀寫內(nèi)存操作,處理器在執(zhí)行內(nèi)存的讀寫操作時(shí),在必要的情況下會(huì)往bus中發(fā)送特定的請(qǐng)求消息,每個(gè)處理器攔截這些消息,在一定情況下往bus回復(fù)消息。


        如下圖,就是MESI的概要圖。


        通過上面的理論,還不好解釋MESI協(xié)議在CPU多核中到底是如何運(yùn)用的,我會(huì)在下面通過幾張圖片的案例,來詳細(xì)剖析它協(xié)議的轉(zhuǎn)換過程。


        如下案例,在內(nèi)存中存在一個(gè)count共享變量,現(xiàn)在 "線程0" 需要用到該變量,那么 "線程0" 會(huì)從內(nèi)存中通過bus總線將變量副本拷貝到緩存中,需要注意的是,線程都會(huì)對(duì)bus總線進(jìn)行監(jiān)聽,例如 "線程0" 讀取了共享變量,因?yàn)?"線程1"對(duì)bus總線進(jìn)行了監(jiān)聽,所以它是知道的 "線程0" 進(jìn)行了讀取操作。但是目前為止該緩存變量它的狀態(tài)為 "E(獨(dú)占)",為什么為獨(dú)占?因?yàn)樵撟兞恐槐灰粋€(gè)線程所使用。


        接著,"線程1" 也需要用到該共享變量,它同樣也會(huì)通過bus總線去內(nèi)存中拷貝變量副本,這時(shí) "線程0" 監(jiān)聽到 "線程1" 也拷貝了共享變量副本,此時(shí) "線程0" 它會(huì)將內(nèi)部的變量狀態(tài)標(biāo)識(shí)改為 "S(共享)",而 "線程1" 這邊也會(huì)將變量狀態(tài)標(biāo)識(shí)為 "S(共享)" ,那么此時(shí)線程就不能夠隨便的對(duì)變量進(jìn)行修改了,因?yàn)樵撟兞勘欢鄠€(gè)線程所使用,所以CPU需要同時(shí)對(duì)兩個(gè)線程中的變量進(jìn)行維護(hù)。


        然后接著,"線程1" 對(duì)變量了進(jìn)行修改,此時(shí) "線程1" 會(huì)將共享變量的狀態(tài)標(biāo)識(shí)改為 "M(修改)",并通知bus總線該變量已經(jīng)發(fā)生了修改,那么這時(shí)候 "線程0" 監(jiān)聽到了 "線程1" 修改了共享變量, "線程0" 就會(huì)將變量狀態(tài)標(biāo)識(shí)改為 "I(丟棄)",當(dāng)CPU識(shí)別到 "線程1" 變量狀態(tài)標(biāo)識(shí)為i的時(shí)候,就會(huì)將該變量從緩存中進(jìn)行丟棄,重新去內(nèi)存中拷貝最新的變量副本。


        但是此刻會(huì)衍生出另外一個(gè)問題,假如兩個(gè)線程同時(shí)進(jìn)行對(duì)變量進(jìn)行了修改,那么到底是哪個(gè)線程修改成功呢?還是說晚修改的變量會(huì)對(duì)早修改的變量就行覆蓋?那這樣豈不是會(huì)造成臟數(shù)據(jù)的發(fā)生?
        針對(duì)以上這種情況當(dāng)然是不允許發(fā)生的啦,如果線程需要對(duì)變量進(jìn)行修改,會(huì)先在本地的緩存行中上一個(gè)lock鎖(本地寫緩存行),因?yàn)閿?shù)據(jù)都是存放在緩存行中的,但是會(huì)有這么一個(gè)問題,它們是對(duì)各自的緩存行進(jìn)行上鎖,其他的線程是并不知道,還是無法解決多個(gè)線程同時(shí)操作造成臟數(shù)據(jù)的發(fā)生,但是CPU也考慮到緩存一致性的問題,假如多個(gè)線程都對(duì)各自的緩存行進(jìn)行了上鎖,也同時(shí)發(fā)送本地寫緩存行消息給了bus總線,那么此時(shí)就會(huì)由bus總線來決定,由某個(gè)線程來進(jìn)行修改。


        以上是針對(duì)正常的情況,MESI協(xié)議能夠正常的對(duì)緩存行進(jìn)行狀態(tài)標(biāo)識(shí)轉(zhuǎn)換,那么我們來聊一聊針對(duì)非正常情況,MESI協(xié)議是否還適用呢?
        在CPU緩存中,它的緩存大小為64個(gè)字節(jié),假如我們現(xiàn)在內(nèi)存中有一個(gè)大對(duì)象,它的大小為124個(gè)字節(jié),那么在CPU中一個(gè)緩存行是無法進(jìn)行存儲(chǔ)的,它會(huì)將變量存儲(chǔ)在兩個(gè)緩存中,這樣的話CPU在對(duì)變量進(jìn)行操作就不再是原子操作,MESI協(xié)議無法同時(shí)對(duì)線程內(nèi)的兩個(gè)緩存行進(jìn)行l(wèi)ock加鎖,這時(shí)MESI協(xié)議失效,緩存行鎖失效,從而晉升到bus總線鎖。


        總線鎖的概念:將總線鎖住,只有一個(gè)內(nèi)核線程能夠操作該變量,其他的線程只能默默的看著它進(jìn)行操作啦,它的壞處在于,由多核的CPU變成了單核CPU操作效率大幅度的降低。


        不知道大家伙看到這,對(duì)CPU底層對(duì)多線程數(shù)據(jù)處理,以及它的安全性問題是否有一個(gè)比較清晰的認(rèn)知了呢?關(guān)于CPU底層有很多涉及到硬件層面的內(nèi)容啦,大家伙感興趣可自行查閱相關(guān)文檔哦~


        我是黎明大大,我知道我沒有驚世的才華,也沒有超于凡人的能力,但畢竟我還有一個(gè)不屈服,敢于選擇向命運(yùn)沖鋒的靈魂,和一個(gè)就是傷痕累累也要義無反顧走下去的心。


        如果您覺得本文對(duì)您有幫助,還請(qǐng)關(guān)注點(diǎn)贊一波,后期將不間斷更新更多技術(shù)文章


        掃描二維碼關(guān)注我
        不定期更新技術(shù)文章哦



        JUC并發(fā)編程之單例模式雙重檢驗(yàn)鎖陷阱

        JUC并發(fā)編程之Volatile關(guān)鍵字詳解

        JUC并發(fā)編程之JMM內(nèi)存模型詳解

        深入Hotspot源碼與Linux內(nèi)核理解NIO與Epoll

        基于Python爬蟲爬取有道翻譯實(shí)現(xiàn)翻譯功能

        JAVA集合之ArrayList源碼分析

        Mysql幾種join連接算法



        發(fā)現(xiàn)“在看”和“贊”了嗎,因?yàn)槟愕狞c(diǎn)贊,讓我元?dú)鉂M滿哦
        瀏覽 99
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 极品少妇馒头坹66P | 午夜性刺激免费视频 | 午夜羞羞| 性高潮自述 | 国产偷窥熟女精品大全 |