摘要:線程安全性深層原因這里我們將會從計(jì)算機(jī)硬件和編輯器等方面來詳細(xì)了解線程安全產(chǎn)生的深層原因。類似這種不影響單線程語義的亂序執(zhí)行我們稱為指令重排。通過線程安全性深層原因我們能更好的理解這三大性質(zhì)的根本性原因。上一篇并發(fā)編程線程基礎(chǔ)查漏補(bǔ)缺
線程安全性深層原因
這里我們將會從計(jì)算機(jī)硬件和編輯器等方面來詳細(xì)了解線程安全產(chǎn)生的深層原因。
緩存一致性問題 CPU內(nèi)存架構(gòu)隨著CPU的發(fā)展,而因?yàn)镃PU的速度和內(nèi)存速度不匹配的問題(CPU寄存器的訪問速度非常快,而內(nèi)存訪問速度相對偏慢),所有在CPU和內(nèi)存之間出現(xiàn)了多級高速緩存。下圖是現(xiàn)代CPU和內(nèi)存的一般架構(gòu)圖:
我們可以看到高速緩存也分為三級緩存,越靠近寄存器的級別緩存訪問速度越快。其中L3 Cache為多核共享的,L1和L2 Cache為單核獨(dú)享,而L1又有數(shù)據(jù)緩存(L1 d)和指令緩存(L1 i)。
正因?yàn)楦咚倬彺娴某霈F(xiàn),各CPU內(nèi)核從主內(nèi)存獲取相同的數(shù)據(jù)將會存在于緩存中,當(dāng)多核都對此數(shù)據(jù)進(jìn)行操作并修改值,此時(shí)另外的核心并不知道此值已被其他核心修改,從而出現(xiàn)緩存不一致的問題。
如何解決緩存一致性問題解決緩存一致性問題一般有兩個(gè)方法:
第一個(gè)是采用總線鎖,在總線級別加鎖,這樣從內(nèi)存種訪問到的數(shù)據(jù)將被當(dāng)個(gè)CPU核心獨(dú)占,在多核的情況下對單個(gè)資源將是串行化的。這種方式性能上將大打折扣。
第二個(gè)是采用緩存鎖,在緩存的級別上進(jìn)行加鎖。此種方式需要某種協(xié)議對緩存行數(shù)據(jù)進(jìn)行同步,后面所說的緩存一致行協(xié)議便是一種實(shí)現(xiàn)。
緩存一致性協(xié)議(MESI)為了解決緩存一致性的問題,一些CPU系列(比如Intel奔騰系列)采用了MESI協(xié)議來解決緩存一致性問題。此協(xié)議將每個(gè)緩存行(Cache Line)使用4種狀態(tài)進(jìn)行標(biāo)記。
M: 被修改(Modified)
該緩存行只被緩存在該CPU核心的緩存中,并且是被修改過的(dirty),即與主存中的數(shù)據(jù)不一致,該緩存行中的內(nèi)存需要在未來的某個(gè)時(shí)間點(diǎn)(允許其它CPU讀取請主存中相應(yīng)內(nèi)存之前)寫回(write back)主存。當(dāng)被寫回主存之后,該緩存行的狀態(tài)會變成獨(dú)享(exclusive)狀態(tài)。
E: 獨(dú)享的(Exclusive)
該緩存行只被緩存在該CPU核心緩存中,它是未被修改過的(clean),與主存中數(shù)據(jù)一致。該狀態(tài)可以在任何時(shí)刻當(dāng)有其它CPU核心讀取該內(nèi)存時(shí)變成共享狀態(tài)(shared)。同樣地,當(dāng)CPU核心修改該緩存行中內(nèi)容時(shí),該狀態(tài)可以變成Modified狀態(tài)。
S: 共享的(Shared)
該狀態(tài)意味著該緩存行可能被多個(gè)CPU緩存,并且各個(gè)緩存中的數(shù)據(jù)與主存數(shù)據(jù)一致(clean),當(dāng)有一個(gè)CPU修改該緩存行中,其它CPU中該緩存行可以被作廢(變成無效狀態(tài)(Invalid))。
I: 無效的(Invalid)
該緩存是無效的(可能有其它CPU核心修改了該緩存行)
在MESI協(xié)議中,每個(gè)CPU核心的緩存控制器不僅知道自己的操作(local read和local write),每個(gè)核心的緩存控制器通過監(jiān)聽也知道其他CPU中cache的操作(remote read和remote write),再確定自己cache中共享數(shù)據(jù)的狀態(tài)是否需要調(diào)整。
local read(LR):讀本地cache中的數(shù)據(jù);
local write(LW):將數(shù)據(jù)寫到本地cache;
remote read(RR):其他核心發(fā)生read;
remote write(RW):其他核心發(fā)生write;
針對操作,緩存行的狀態(tài)遷移圖如下:
在我們編程過程中,習(xí)慣性程序思維認(rèn)為程序是按我們寫的代碼順序執(zhí)行的,舉個(gè)例子來說,某個(gè)程序中有三行代碼:
int a = 1; // 1 int b = 2; // 2 int c = a + b; // 3
從程序員角度執(zhí)行順序應(yīng)該是1 -> 2 -> 3,實(shí)際經(jīng)過編譯器和CPU的優(yōu)化很有可能執(zhí)行順序會變成 2 -> 1 -> 3(注意這樣的優(yōu)化重排并沒有改變最終的結(jié)果)。類似這種不影響單線程語義的亂序執(zhí)行我們稱為指令重排。(后面講Java內(nèi)存模型也會講到這部分。)
編譯器指令重排舉個(gè)例子,我們先看可以看一段代碼:
class ReorderExample { int a = 0; boolean flag = false; public void write() { a = 1; // 1 flag = true; // 2 } public void read() { if (flag) { // 3 int i = a * a; // 4 } } }
在單線程的情況下如果先write再read的話,i的結(jié)果應(yīng)該是1。但是在多線程的情況下,編譯器很可能對指令進(jìn)行重排,有可能出現(xiàn)的執(zhí)行順序是2 -> 3 -> 4 -> 1。這個(gè)時(shí)候的i的結(jié)果就是0了。(1和2之間以及3和4之間不存在數(shù)據(jù)依賴,有關(guān)數(shù)據(jù)依賴在后面的Java內(nèi)存模型中會講到。)
CPU指令重排在CPU層面,一條指令被分為多個(gè)步驟來執(zhí)行,每個(gè)步驟會使用不同的硬件(比如寄存器、存儲器、算術(shù)邏輯單元等)。執(zhí)行多個(gè)指令時(shí)采用流水線技術(shù)進(jìn)行執(zhí)行,如下示意圖:
注意這里出現(xiàn)的”停頓“,出現(xiàn)這個(gè)原因是因?yàn)椴襟E22需要步驟13得到結(jié)果后才能進(jìn)行。CPU為了進(jìn)一般優(yōu)化:消除一些停頓,這時(shí)會將指令3(指令3對指令2和1都沒有數(shù)據(jù)依賴)移到指令2之前進(jìn)行運(yùn)行。這樣就出現(xiàn)了指令重排,根本原因是為了優(yōu)化指令的執(zhí)行。
CPU經(jīng)過長時(shí)間的優(yōu)化,在寄存器和L1緩存之間添加了LoadBuffer、StoreBuffer來降低阻塞時(shí)間。LoadBuffer、StoreBuffer,合稱排序緩沖(Memoryordering Buffers (MOB)),Load緩沖64長度,store緩沖36長度,Buffer與L1進(jìn)行數(shù)據(jù)傳輸時(shí),CPU無須等待。
CPU執(zhí)行l(wèi)oad讀數(shù)據(jù)時(shí),把讀請求放到LoadBuffer,這樣就不用等待其它CPU響應(yīng),先進(jìn)行下面操作,稍后再處理這個(gè)讀請求的結(jié)果。
CPU執(zhí)行store寫數(shù)據(jù)時(shí),把數(shù)據(jù)寫到StoreBuffer中,待到某個(gè)適合的時(shí)間點(diǎn),把StoreBuffer的數(shù)據(jù)刷到主存中。
因?yàn)镾toreBuffer的存在,CPU在寫數(shù)據(jù)時(shí),真實(shí)數(shù)據(jù)并不會立即表現(xiàn)到內(nèi)存中,所以對于其它CPU是不可見的;同樣的道理,LoadBuffer中的請求也無法拿到其它CPU設(shè)置的最新數(shù)據(jù);由于StoreBuffer和LoadBuffer是異步執(zhí)行的,所以在外面看來,先寫后讀,還是先讀后寫,沒有嚴(yán)格的固定順序。
由于引入StoreBuffer和LoadBuffer導(dǎo)致異步模式,從而導(dǎo)致內(nèi)存數(shù)據(jù)的讀寫可能是亂序的(也就是內(nèi)存系統(tǒng)的重排序)。
內(nèi)存屏障為了解決CPU優(yōu)化帶來的不可見、重排序的問題,可以使用內(nèi)存屏障(memory barrier)來阻止一定的優(yōu)化(在后面介紹Java內(nèi)存模型也會詳細(xì)結(jié)合講內(nèi)存屏障)。不同的CPU架構(gòu)對內(nèi)存屏障的實(shí)現(xiàn)方式與實(shí)現(xiàn)程度非常不一樣,下面我們看下X86架構(gòu)中內(nèi)存屏障的實(shí)現(xiàn)。
Store Barrier使所有Store Barrier之前發(fā)生的內(nèi)存更新都是可見的。
Load Barrier使所有Store Barrier之前發(fā)生的內(nèi)存更新,對Load Barrier之后的load操作都是可見的。
Full Barrier所有Full Barrier之前發(fā)生的操作,對所有Full Barrier之后的操作都是可見的。
延伸在程序我們常說的三大性質(zhì):可見性、原子性、有序性。通過線程安全性深層原因我們能更好的理解這三大性質(zhì)的根本性原因。(可見性、原子性、有序性會在后面文章中進(jìn)行詳細(xì)講解。)
上一篇:Java并發(fā)編程——線程基礎(chǔ)查漏補(bǔ)缺
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72746.html
摘要:兜底任務(wù),處理數(shù)據(jù)不一致狀態(tài)的任務(wù)。什么是多線程多線程是并發(fā)的一種重要形式。通過具體的多線程問題引出多線程編程中的關(guān)鍵點(diǎn)和對應(yīng)的工具與知識點(diǎn),輕松學(xué)會多線程編程。 這篇文章的目的并不是想教你如何造火箭(面試造火箭,工作擰螺絲),而是想通過對原理和應(yīng)用案例的有限度剖析來協(xié)助你構(gòu)建起并發(fā)的思維,并將操作系統(tǒng)的理論知識與工程實(shí)踐結(jié)合起來,貫穿從學(xué)到會的全過程。當(dāng)然,雖然我們是從實(shí)用角度出發(fā),...
摘要:并發(fā)模塊本身有兩種不同的類型進(jìn)程和線程,兩個(gè)基本的執(zhí)行單元。調(diào)用以啟動新線程。在大多數(shù)系統(tǒng)中,時(shí)間片發(fā)生不可預(yù)知的和非確定性的,這意味著線程可能隨時(shí)暫停或恢復(fù)。 大綱 什么是并發(fā)編程?進(jìn)程,線程和時(shí)間片交織和競爭條件線程安全 策略1:監(jiān)禁 策略2:不可變性 策略3:使用線程安全數(shù)據(jù)類型 策略4:鎖定和同步 如何做安全論證總結(jié) 什么是并發(fā)編程? 并發(fā)并發(fā)性:多個(gè)計(jì)算同時(shí)發(fā)生。 在現(xiàn)代...
摘要:不可變在中,不可變的對象一定是線程安全的。在里標(biāo)注自己是線程安全的類,大多都不是絕對線程安全,比如某些情況下類在調(diào)用端也需要額外的同步措施。無同步方案要保證線程安全,不一定就得需要數(shù)據(jù)的同步,兩者沒有因果關(guān)系。 在之前學(xué)習(xí)編程的時(shí)候,有一個(gè)概念根深蒂固,即程序=算法+數(shù)據(jù)結(jié)構(gòu)。數(shù)據(jù)代表問題空間中的客體,代碼就用來處理這些數(shù)據(jù),這種思維是站在計(jì)算機(jī)的角度去抽象問題和解決問題,稱之為面向過...
摘要:在中一般來說通過來創(chuàng)建所需要的線程池,如高并發(fā)原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細(xì)原理解析 - 后端 - 掘金今天我們來研究學(xué)習(xí)一下AbstractQueuedSynchronizer類的相關(guān)原理,java.util.concurrent包中很多類都依賴于這個(gè)類所提供的隊(duì)列式...
摘要:在中一般來說通過來創(chuàng)建所需要的線程池,如高并發(fā)原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細(xì)原理解析 - 后端 - 掘金今天我們來研究學(xué)習(xí)一下AbstractQueuedSynchronizer類的相關(guān)原理,java.util.concurrent包中很多類都依賴于這個(gè)類所提供的隊(duì)列式...
閱讀 2891·2021-11-24 09:39
閱讀 2460·2019-08-30 15:53
閱讀 3033·2019-08-30 13:47
閱讀 1305·2019-08-30 12:50
閱讀 1486·2019-08-29 16:31
閱讀 2647·2019-08-29 13:14
閱讀 1564·2019-08-29 10:55
閱讀 798·2019-08-26 13:32