java硬件编程_关于JAVA并发编程你需要知道的——硬件篇
無(wú)論程序語(yǔ)言如何千變?nèi)f化,他們都深深地根植于目前的計(jì)算機(jī)體系結(jié)構(gòu)。
左圖是intel CPU的三級(jí)高速緩存設(shè)計(jì),由于高速緩存對(duì)程序員基本不可見(jiàn),因此可以抽象為右圖。
緩存的設(shè)計(jì)
首先還是先談?wù)勛髨D。
L1-cache分為兩部分,i-cache存儲(chǔ)指令(只讀),d-cache存儲(chǔ)數(shù)據(jù)(可讀可寫(xiě))
CPU只能和寄存器以及L1-cache進(jìn)行直接交互,數(shù)據(jù)不能隔層傳遞,只能一層一層往上讀,一層一層往下寫(xiě)
訪(fǎng)問(wèn)L1需要至少4個(gè)時(shí)鐘周期,L2需要至少10個(gè),L3需要至少30個(gè)。即便是速度最快的L1,也低于運(yùn)算單元的執(zhí)行速度,何況存在緩存未命中的情況,因此在L1和運(yùn)算單元之間加上了Writebuffer和Readbuffer(合稱(chēng)Memory Ordering Buffer,MOB),數(shù)據(jù)準(zhǔn)備好的時(shí)候再完成相關(guān)指令,這就是CPU指令亂序——順序執(zhí)行亂序完成。亂序完成的結(jié)果放入到Writebuffer中,按照原有的執(zhí)行順序,刷到緩存中
緩存由多個(gè)緩存行組成。每個(gè)緩存行結(jié)構(gòu)如圖所示(以64位機(jī)器為例)
CPU讀取緩存的時(shí)候找到對(duì)應(yīng)的緩存行,如果前面的有效位為零,就從下一級(jí)緩存加載到這一級(jí)緩存
相關(guān)的問(wèn)題
明白緩存的設(shè)計(jì)之后,再看右圖來(lái)分析其中的問(wèn)題
緩存導(dǎo)致的內(nèi)存可見(jiàn)性:已知線(xiàn)程A運(yùn)行在core 0上,線(xiàn)程B運(yùn)行在core 1上,兩者都對(duì)同一個(gè)內(nèi)存地址進(jìn)行讀取,這個(gè)內(nèi)存地址的內(nèi)容會(huì)被加載到cache,然后CPU讀取,這時(shí)候線(xiàn)程A對(duì)內(nèi)容進(jìn)行了修改,但是線(xiàn)程B卻可能一直從本核心的cache讀取,無(wú)法感知到該地址的內(nèi)容已被修改。
多核導(dǎo)致的自增操作原子性:自增操作分為三步:從內(nèi)存讀取變量到寄存器;寄存器中的值加1;寫(xiě)回到內(nèi)存。已知線(xiàn)程A運(yùn)行在core 0上,線(xiàn)程B運(yùn)行在core 1上,兩者都對(duì)變量執(zhí)行加一操作。A執(zhí)行完一二兩步時(shí),B執(zhí)行完第一步,A將加一后的值寫(xiě)入到內(nèi)存,B執(zhí)行完二三兩步也將加一后的值寫(xiě)入到內(nèi)存,結(jié)果變量只加了一,而不是加二
MOB導(dǎo)致的cache可見(jiàn)性:a=1.0; a=a/2; a=a-1.0;按照正常的邏輯,a最后的結(jié)果為-0.5;但是因?yàn)槌ǖ膱?zhí)行時(shí)鐘周期大于減法,第三句執(zhí)行時(shí),a/2的結(jié)果存放在writebuffer中還沒(méi)寫(xiě)入到緩存,a-1.0中a的值已經(jīng)從緩存中加載到readbuffer,也就是a-1.0=1.0-1.0=0 (高級(jí)語(yǔ)言不會(huì)出現(xiàn)這個(gè)問(wèn)題,因?yàn)榫幾g器已經(jīng)做了處理,前面的偽代碼僅表示邏輯)
相關(guān)的實(shí)現(xiàn)
為了解決這些問(wèn)題,CPU提供了一些指令,其中比如lock和cmpxchg。
lock 匯編前綴,在Intel奔騰系列之前,這個(gè)指令前綴能夠鎖定總線(xiàn),禁止其他CPU核心操作內(nèi)存,執(zhí)行完后邊的指令后釋放總線(xiàn),在這個(gè)過(guò)程中其他CPU核心會(huì)監(jiān)聽(tīng)總線(xiàn),發(fā)現(xiàn)某個(gè)內(nèi)存地址內(nèi)容被修改,就會(huì)將本核心下的對(duì)應(yīng)cache行有效位置0。因?yàn)殒i總線(xiàn)會(huì)禁止所有內(nèi)存操作,降低效率,因此在奔騰之后,這個(gè)指令前綴不鎖總線(xiàn)而是鎖定相關(guān)的cache行,對(duì)某個(gè)地址修改后直接讓相關(guān)cache失效。這樣解決了問(wèn)題一
cmpxchg 將寄存器a中的值與內(nèi)存中比較如果一樣,將寄存器c中的值和內(nèi)存中的值交換,如果不一樣就設(shè)置異常位并將內(nèi)存中的值讀取到寄存器a。代入到問(wèn)題二,從內(nèi)存讀取值到寄存器a,加一后保存到寄存器c,然后執(zhí)行cmpxchg,執(zhí)行完后如果有異常,就重新加一,再?lài)L試寫(xiě)回,直到成功。這樣解決了問(wèn)題二
cmpxchg和lock 在執(zhí)行完以后會(huì)將writebuffer刷到cache并清空readbuffer,這樣解決了問(wèn)題三。另外X86_64引入了內(nèi)存屏障指令 lfence、sfence、mfence。lfence前面的讀取操作完成,也就是readbuffer中的內(nèi)容全部被cpu讀取后,才能執(zhí)行l(wèi)fence之后的讀取操作;sfence前面的寫(xiě)入操作完成,也就是writebuffer全部刷到緩存中,才能執(zhí)行sfence之后的寫(xiě)入操作;mfence之前的讀寫(xiě)操作全部完成,才能進(jìn)行mfence之后的操作。
總結(jié)
以上是生活随笔為你收集整理的java硬件编程_关于JAVA并发编程你需要知道的——硬件篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 企业信息安全整体架构
- 下一篇: 中台建设方案