自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入理解并發(fā)編程藝術(shù)之計算機(jī)內(nèi)存模型

開發(fā) 前端
隨著計算機(jī)高速發(fā)展,CPU 技術(shù)遠(yuǎn)超過內(nèi)存技術(shù),所以多級緩存被使用,解決了內(nèi)存和 cpu 的讀寫速度問題,隨著多線程的發(fā)展,緩存一致性問題油然而生,好在可以通過緩存一致性協(xié)議來解決,比較出名的緩存一致性協(xié)議是MESI,MESI協(xié)議的引入,微微降低了 cpu 的速度。

了解java內(nèi)存模型不得不先了解計算機(jī)內(nèi)存模型,我們接下來就從計算內(nèi)存模型說起

計算機(jī)發(fā)展

我們都知道 CPU 和 內(nèi)存是計算機(jī)中比較核心的兩個東西,任何在計算機(jī)上運行的程序其實都是對數(shù)據(jù)的存取和處理計算,最終都會映射成cpu和內(nèi)存之間的頻繁交互,最原始計算機(jī)就是cpu讀取內(nèi)存進(jìn)行處理,然后回寫內(nèi)存。

CPU在摩爾定律的指導(dǎo)下以每18個月翻一番的速度在發(fā)展,cpu的處理速度不斷增速,其處理速度遠(yuǎn)遠(yuǎn)超出了內(nèi)存的讀寫速度,導(dǎo)致的后果就是cpu大量的時間都花費在磁盤 I/O、網(wǎng)絡(luò)通信或者數(shù)據(jù)庫訪問上,cpu大部分的時間都處于空閑的等待狀態(tài)。

為了充分壓榨cpu的性能,避免cpu性能浪費,就必須使用一些手段去把處理器的運算能力“壓榨”出來,最容易想到的就是讓計算機(jī)同時處理幾項任務(wù)。為了實現(xiàn)這一目標(biāo),計算機(jī)系統(tǒng)不得不加入一層或多層讀寫速度盡可能接近處理器運算速度的高速緩存(Cache)來作為內(nèi)存與處理器之間的緩沖:將運算需要使用的數(shù)據(jù)復(fù)制到緩存中,讓運算能快速進(jìn)行,當(dāng)運算結(jié)束后再從緩存同步回內(nèi)存之中,這樣處理器就無須等待緩慢的內(nèi)存讀寫了。

圖片圖片

上圖為計算機(jī)多核cpu多級緩存圖,即當(dāng)下流行的cpu架構(gòu),計算機(jī)內(nèi)存模型主要涉及到的組件:處理器,寄存器,高速緩存,內(nèi)存,緩存行。

處理器:負(fù)責(zé)做邏輯運算,程序代碼都會變成運算指令或計算公式,在處理器里面其實就是二進(jìn)制的各種組合,處理器計算后會得到一個結(jié)果。

寄存器:離處理器最近的一塊存儲介質(zhì),可以說位于內(nèi)存模型的頂端,它的速度非常之快,快到可以和處理器相媲美,處理器從里面拿數(shù)據(jù),運算完之后又把數(shù)據(jù)存回去。寄存器是處理器里面的一部分,處理器可能有多個寄存器,比如數(shù)據(jù)計數(shù)器,指令指針寄存器等等。

高速緩存:是一個比內(nèi)存速度快很多接近處理器速度的存儲區(qū)域,目的是把處理器要用到的一堆數(shù)據(jù)從主內(nèi)存中復(fù)制進(jìn)來供處理器使用,處理器運算處理完了之后又把結(jié)果同步回主內(nèi)存,這樣處理器只做自己的事,而高速緩存就成了傳話筒。高速緩存有分為一級緩存,二級緩存和三級緩存,離處理器最近的是一級緩存,依次往后排。存儲器存儲空間大?。簝?nèi)存>L3>L2>L1>寄存器存儲器速度快慢排序:寄存器>L1>L2>L3>內(nèi)存

緩存行:緩存是由最小的存儲區(qū)塊-緩存行(cacheline)組成,緩存行大小通常為64byte。緩存行是什么意思呢?比如你的L1緩存大小是512kb,而cacheline = 64byte,那么就是L1里有512 * 1024/64個cacheline,也是cpu中寄存器從緩存中取數(shù)據(jù)的最小單位,即取數(shù)為x=0,那么在緩存中找到x=0后不是只把x=0取走,而是把x=0所在的緩存行取走。

內(nèi)存:就是我們通常講的內(nèi)存,比如現(xiàn)在的電腦動不動8G,16G啊等等,在內(nèi)存模型中叫做主內(nèi)存,它比磁盤的讀寫速度快很多,但是又跟高速緩存沒法比,因此,程序啟動的時候,程序相關(guān)的數(shù)據(jù)會加載到主內(nèi)存,然后處理器處理某塊邏輯的時候,比較占空間的東西會丟到主內(nèi)存,比如Java里面的對象,就是存放在堆上面的,而Java虛擬機(jī)里面的堆就是放在主內(nèi)存的。

在CPU訪問存儲設(shè)備時會遵循一定的原理,無論是存取數(shù)據(jù)抑或存取指令,都趨于聚集在一片連續(xù)的區(qū)域中,這就是局部性原理。這也是cpu架構(gòu)提高性能的一個關(guān)鍵性因素。

時間局部性(Temporal Locality):如果一個信息項正在被訪問,那么在近期它很可能還會被再次訪問。比如循環(huán)、遞歸、方法的反復(fù)調(diào)用等。

空間局部性(Spatial Locality):如果一個存儲器的位置被引用,那么將來他附近的位置也會被引用。比如順序執(zhí)行的代碼、連續(xù)創(chuàng)建的兩個對象、數(shù)組等。

帶有高速緩存的CPU執(zhí)行計算的流程:1.程序以及數(shù)據(jù)被加載到主內(nèi)存2.指令和數(shù)據(jù)被加載到CPU的高速緩存3.CPU執(zhí)行指令,把結(jié)果寫到高速緩存4.高速緩存中的數(shù)據(jù)寫回主內(nèi)存

講到這里我們知道以上新型cpu架構(gòu)是為充分壓榨cpu性能而來,那么就單看以上架構(gòu),在不做任何優(yōu)化的情況下,當(dāng)多核cpu并發(fā)工作的時候必然會引入緩存一致性問題。在多路處理器系統(tǒng)中,每個處理器都有自己的高速緩存,而它們又共享同一主內(nèi)存,當(dāng)多個處理器的運算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時,將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致。如果真的發(fā)生這種情況,那同步回到主內(nèi)存時該以誰的緩存數(shù)據(jù)為準(zhǔn)呢?例如:假設(shè)主內(nèi)存中存在一個共享變量 x,現(xiàn)在有 A 和 B 兩個內(nèi)核(也可以直接說分布在兩個核上的線程)分別對該變量 x=1 進(jìn)行操作,A/B 核各自高速緩存中存在共享變量副本 x。假設(shè)現(xiàn)在 A 想要修改 x 的值為 2,而 B 卻想要讀取 x 的值,那么 B 讀取到的值是 A 更新后的值 2,還是更新前的值 1 呢?答案是,不確定,即 B 有可能讀取到 A 更新前的值 1,也有可能讀取到 A 更新后的值 2,這是因為高速緩存是每個核私有的數(shù)據(jù)區(qū)域,而 A 在操作變量 x 時,首先是將變量從主內(nèi)存拷貝到 A 的高速緩存中,然后對變量進(jìn)行操作,操作完成后再將變量 x 寫回主內(nèi)存,而對于 B 也是類似的,這樣就有可能造成主內(nèi)存與工作內(nèi)存間數(shù)據(jù)存在一致性問題,假如 A 修改完后正在將數(shù)據(jù)寫回主內(nèi)存,而 B 此時正在讀取主內(nèi)存,即將 x=1 拷貝到自己的工作高速緩存中,這樣 B 讀取到的值就是 x=1,但如果 A 已將 x=2 寫回主內(nèi)存后,B 才開始讀取的話,那么此時 B 讀取到的就是 x=2,但到底是哪種情況先發(fā)生呢,在并發(fā)訪問過程中這些都是不確定的。

除了增加高速緩存之外,為了使處理器內(nèi)部的運算單元能盡量被充分利用,處理器可能會對輸入代碼進(jìn)行亂序執(zhí)行優(yōu)化,處理器會在計算之后將亂序執(zhí)行的結(jié)果重組,保證該結(jié)果與順序執(zhí)行的結(jié)果是一致的,但并不保證程序中各個語句計算的先后順序與輸入代碼中的順序一致,因此如果存在一個計算任務(wù)依賴另外一個計算任務(wù)的中間結(jié)果,那么其順序性并不能靠代碼的先后順序來保證,顧名思義,當(dāng)單線程運行的時候,無論怎樣亂序,最終的結(jié)果都是預(yù)期的結(jié)果,但是當(dāng)多線程的時候呢,就不一定了,特別是存在共享變量的或者說一個線程依賴于另一個線程的計算結(jié)果的時候,就很有可能因為亂序帶來不正確的結(jié)果。

通過以上可以得知,cpu架構(gòu)自身存在數(shù)據(jù)一致性的問題和亂序重排問題,其實也可以理解為java的并發(fā)訪問的原子性問題,可見性問題,有序性問題。

計算機(jī)內(nèi)存模型

在多核cpu架構(gòu)中,每個核心都有自己的L1 L2高速緩存,同個cpu的多個核心共享L3緩存,不同cpu之間共享主內(nèi)存,為了保證共享內(nèi)存的正確性,內(nèi)存模型定義了共享內(nèi)存系統(tǒng)中多線程程序讀寫操作行為的規(guī)范。通過這些規(guī)則來規(guī)范對內(nèi)存的讀寫操作,從而保證指令執(zhí)行的正確性。它與處理器有關(guān)、與緩存有關(guān)、與并發(fā)有關(guān)、與編譯器也有關(guān)。他解決了 CPU 多級緩存、處理器優(yōu)化、指令重排等導(dǎo)致的內(nèi)存訪問問題,保證了并發(fā)場景下的一致性、原子性和有序性。

內(nèi)存模型解決并發(fā)問題主要采用兩種方式:1.限制處理器優(yōu)化2.使用內(nèi)存屏障

我們來看下內(nèi)存模型的具體做法

解決緩存不一致問題

解決緩存不一致的方法有很多,比如:總線加鎖(此方法性能較低,現(xiàn)在已經(jīng)不會再使用)MESI 協(xié)議:當(dāng)一個 CPU 修改了 Cache 中的數(shù)據(jù),會通知其他緩存了這個數(shù)據(jù)的 CPU,其他 CPU 會把 Cache 中這份數(shù)據(jù)的 Cache Line 置為無效,要讀取數(shù)據(jù)的話,直接去內(nèi)存中獲取,不會再從 Cache 中獲取了。當(dāng)然還有其他的解決方案,MESI 協(xié)議是其中比較出名的。

MESI 協(xié)議中的狀態(tài)CPU 中每個緩存行使用的 4 種狀態(tài)進(jìn)行標(biāo)記(使用額外的兩位 bit 表示)

圖片圖片

  • M 和 E 的數(shù)據(jù)都是本 core 獨有的,不同之處是 M 狀態(tài)的數(shù)據(jù)是 dirty(和內(nèi)存中的不一致),E 狀態(tài)的數(shù)據(jù)是 clean(和內(nèi)存中的一致)
  • S 狀態(tài)是所有 Core 的數(shù)據(jù)都是共享的,只有 clean 的數(shù)據(jù)才能被多個 core 共享
  • I-表示這個 Cache line 無效

E 狀態(tài)只有 Core 0 訪問變量 x,它的 Cache line 狀態(tài)為 E(Exclusive)。

圖片圖片

S 狀態(tài)3 個 Core 都訪問變量 x,它們對應(yīng)的 Cache line 為 S(Shared)狀態(tài)。

圖片圖片

M 狀態(tài)和I狀態(tài)之間的轉(zhuǎn)化Core 0 修改了 x 的值之后,這個 Cache line 變成了 M(Modified)狀態(tài),其他 Core 對應(yīng)的 Cache line 變成了 I(Invalid)狀態(tài) 在 MESI 協(xié)議中,每個 Cache 的 Cache 控制器不僅知道自己的讀寫操作,而且也監(jiān)聽(snoop)其它 Cache 的讀寫操作。每個 Cache line 所處的狀態(tài)根據(jù)本核和其它核的讀寫操作在 4 個狀態(tài)間進(jìn)行遷移

圖片圖片

MESI 協(xié)議通過標(biāo)識緩存數(shù)據(jù)的狀態(tài),來決定 CPU 何時把緩存的數(shù)據(jù)寫入到內(nèi)存,何時從緩存讀取數(shù)據(jù),何時從內(nèi)存讀取數(shù)據(jù)。

MESI 協(xié)議看似解決了緩存的一致性問題,但是并不那么完美,因為當(dāng)多個緩存對數(shù)據(jù)進(jìn)行了緩存時,一個緩存對數(shù)據(jù)進(jìn)行修改需要同過指令的形式與其他 CPU 進(jìn)行通訊,這個過程是同步的,必須其他 CPU 都把緩存里的數(shù)據(jù)都置為 Invalid 狀態(tài)成功后,我們修改數(shù)據(jù)的 CPU 才能進(jìn)行下一步指令,整個過程中需要同步的和多個緩存通訊,這個過程是不穩(wěn)定的,容易產(chǎn)生問題,而且通訊的過程中 CPU 是必須處于等待的狀態(tài),那么也影響著 CPU 的性能。

為了避免這種 CPU 運算能力的浪費,解決 CPU 切換狀態(tài)阻塞,Store Bufferes 被引入使用。處理器把它想要寫入到主存的值寫到緩存,然后繼續(xù)去處理其他事情。當(dāng)所有失效確認(rèn)都接收到時,數(shù)據(jù)才會最終被提交。

指令重排問題

public class config{
    // 此變量必須定義為
 1   boolean initialized = false;
 2   public Object cache(@NotNull String key) {
 3       if (!initialized) {
 4           doSomethingWithConfig();
 5       }
 6       configText = readConfigFile("pz");
 7       processConfigOptions(configText, "xx");
 8       initialized = true;
 9       if (!initialized) {
 10           doSomethingWithConfig();
        }
    }  
}

拿上面的代碼來說明下亂序,簡單來講就是initialized = false;cpu為了高效,避免再次去緩存取值,很有可能接著執(zhí)行initialized = true(判斷為無依賴關(guān)系的情況下),這個時候6、7行還沒有執(zhí)行,單線程情況下不會有問題,但是并發(fā)情況下就會有問題。下一篇我們詳細(xì)講解。

指令重排序解決方案:硬件工程師其無法預(yù)知未知的程序邏輯場景,所以一些問題還是遺留給了軟件工程師,但是他們給我們提供了一套對應(yīng)場景的解決方案就是“內(nèi)存屏障指令”,我們的軟件工程師可以同內(nèi)存屏障來針對不同場景來選擇性的“禁用緩存”內(nèi)存屏障,又稱內(nèi)存柵欄,是一個CPU指令,硬件分為下面幾種:

lfence(讀屏障 load Barrier):在讀取指令前插入讀屏障,讓緩存中的數(shù)據(jù)失效,重新從主內(nèi)存加載數(shù)據(jù),保證數(shù)據(jù)是最新的。Sfence(寫屏障 store Barrier):在寫入指令后插入屏障,同步把緩存的數(shù)據(jù)寫回內(nèi)存,保證其數(shù)據(jù)立即對其他緩存可見。Mfence(全能屏障):擁有讀屏障和寫屏障的功能。Lock 前綴指令:Lock不是一種內(nèi)存屏障,但是它能完成類似內(nèi)存屏障的功能。Lock會對CPU總線和高速緩存加鎖,可以理解為CPU指令級的一種鎖。它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令。

注意:不同的硬件緩存一致性協(xié)議和內(nèi)存屏障可能不同

總結(jié)

隨著計算機(jī)高速發(fā)展,CPU 技術(shù)遠(yuǎn)超過內(nèi)存技術(shù),所以多級緩存被使用,解決了內(nèi)存和 cpu 的讀寫速度問題,隨著多線程的發(fā)展,緩存一致性問題油然而生,好在可以通過緩存一致性協(xié)議來解決,比較出名的緩存一致性協(xié)議是MESI,MESI協(xié)議的引入,微微降低了 cpu 的速度。

為了更好的壓榨 cpu 的性能,于是Store Bufferes 概念被引入,將 cpu 寫入主存從同步阻塞變?yōu)楫惒?,大大提高?cpu 執(zhí)行效率

指令重排序問題預(yù)期而至,這時候祭出終極武器:內(nèi)存屏障指令,在代碼里面禁用緩存。

至此,計算機(jī)發(fā)展中遇到的問題都一一解決,而這一系列問題解決方案,都是內(nèi)存模型規(guī)范的。

內(nèi)存模型就是為了解決計算機(jī)發(fā)展中遇到的緩存一致性、處理器優(yōu)化和指令重排、并發(fā)編程等問題的一系列規(guī)范,他定義了共享內(nèi)存系統(tǒng)中多線程程序讀寫操作行為的規(guī)范,通過這些規(guī)則來規(guī)范對內(nèi)存的讀寫操作,從而保證指令執(zhí)行的正確性。

責(zé)任編輯:武曉燕 來源: 碼農(nóng)本農(nóng)
相關(guān)推薦

2023-10-27 07:47:58

Java語言順序性

2023-11-05 12:05:35

JVM內(nèi)存

2020-12-11 07:32:45

編程ThreadLocalJava

2020-11-13 08:42:24

Synchronize

2022-10-12 07:53:46

并發(fā)編程同步工具

2022-03-30 15:25:28

鏈接過程計算機(jī)系統(tǒng)程序

2024-05-24 14:35:49

2024-12-31 09:00:12

Java線程狀態(tài)

2019-06-25 10:32:19

UDP編程通信

2015-03-24 13:28:52

Java Java Strin內(nèi)存模型

2022-06-22 08:02:11

CPU操作系統(tǒng)Java

2020-07-02 08:17:12

存儲IO

2021-09-08 17:42:45

JVM內(nèi)存模型

2021-07-26 07:47:37

無鎖編程CPU

2024-03-19 14:14:27

線程開發(fā)

2020-06-01 21:07:33

C11C++11內(nèi)存

2023-09-19 22:47:39

Java內(nèi)存

2013-06-20 10:25:56

2020-11-04 15:35:13

Golang內(nèi)存程序員

2018-06-12 08:53:38

AI內(nèi)存系統(tǒng)
點贊
收藏

51CTO技術(shù)棧公眾號