自旋鎖學習系列(1):一點點硬件知識

java中外部鎖實現就用到了自旋鎖這個概念,以前看jdk的concurrent包源碼的時候,對這部分實現一直是沒法透徹的理解。正所謂」外行看熱鬧,內行看門道「。雖然代碼按行看都能看懂,可是連在一塊兒不知道是爲何這麼作。只能對Doug Lea大神佩服的十體投地。這麼複雜精巧的代碼是怎麼想出來的? java

後面看了《多處理器編程的藝術》這本書時,對自旋的概念開始有了一點概念。要想了解並應用好自旋鎖,不只僅是對相關算法的瞭解,還須要一些些底層硬件的知識哦。不過不用擔憂,須要的底層硬件知識不是不少(萬幸),因此在詳細講解各個自旋鎖以前,須要對這些一點點的硬件知識瞭解一下,正好以此做爲開篇。這個系列大部份內容和代碼都來自於《多處理器編程的藝術》一書,算是一個讀書筆記吧。 程序員

一、處理器和線程

一個多處理器是由多個硬件處理器組成。其中每個處理器都能執行一個順序程序。處理器提取和執行一條指令的時間叫作時鐘週期,這也是咱們用來衡量程序執行性能的基本時間單位。處理器能夠執行線程一段時間,而後不去管這個線程有沒有執行完,轉而去執行另外一個線程。這個切換過程就是咱們熟悉的上下文切換。處理器會由於各類緣由從調度中刪除一個線程去執行其餘的線程。在多處理器系統中,當線程從一個處理器調度中被刪除後,可能從新在另外一個處理器上執行。 算法

二、互連線

互連線是cpu和內存以及cpu和cpu之間進行通訊的一種媒介。有兩種基本的鏈接模式:SMP(對稱duochuli)和 編程

NUMA(非一致性內存訪問)。 數組

在SMP系統結構中,處理器和存儲器經過總線來通訊。處理器和存儲器都有用來負責發送和監聽總線上廣播信息的總線控制單元。SMP系統結構很是廣泛,由於它們比較容易構建。可是對於處理器數量較多的系統來講,這種結構不利於擴展,由於最終總線會成爲瓶頸。 緩存

NUMA中,一系列節點經過點對點網絡鏈接,就像一個小的局域網。每一個節點包含一個或多個處理器和一個存儲器。一個節點的存儲器對於其餘節點來講是能夠訪問的。全部節點的本地存儲器組成了一個全部處理器共享的全局存儲器。NUMA名字叫作非一致性內存訪問,意思就是處理器訪問本身的本地存儲要比訪問其餘處理器的存儲要快。訪問內存的速度並非一致的。NUMA結構顯然比較複雜,須要的協議也更復雜。可是對於處理器數量多的系統來講擴展性更好。 網絡

能夠在SMP和NUMA之間找一個平衡。每一個節點用SMP來構造,而鏈接節點則採用NUMA結構。固然對於咱們程序員來講,咱們只要知道一點:互連線是由處理器共享的有限資源。若是一個處理器佔用了較多的互連線資源,其餘的處理器必然會被延時執行。這一點很是重要。 性能

三、主存

主存能夠當作一個由全部處理器共享的由字組成的一個大數組。咱們經過特定的地址來訪問主存對應的區域。一般字長是32字節或者64字節。字長爲32字節的系統主存地址是32位的,字長爲64字節的系統主存地址是64位的。也就是咱們一般說的32位機和64位機。處理器經過鏈接線給主存發送包含目的地址的信息,用來獲取主存中對應目的地址的值。或者發送包含目的地址以及一個新的數據,用於向主存對應的地址中中寫入新值,當新值被寫入後,主存會發送確認信息。咱們能夠看到,處理器對主存的讀取和寫入都會佔用互連線資源。 測試

四、高速緩存

處理器對主存的一次訪問可能會花費數百個時鐘週期,若是處理器頻繁的對主存進行讀取操做意味着處理器將會花費大量的時間等待主存響應請求。另外,處理器訪問主存會佔用互連線資源,形成其餘的處理器的延遲。因此高速緩存就出現了,這是一個介於處理器和主存之間的一個小容量存儲器。高速緩存的讀取速度比主存要快的多。當處理器要讀取一個值時,首先會到高速緩存中去尋找,若是存在,處理器就不用再去訪問比較慢的主存了,不然處理器必須還要到主存中去取值。咱們把讀取的值在高速緩存中存在這種狀況叫作」cache 命中「,不存在這種狀況叫作」cache缺失「。理解」cache命中「和」cache缺失「對設計高性能的自旋鎖但是很是必要的哦。 spa

五、一致性

當一個處理器讀或寫了被另外一個處理器裝入高速緩存的主存地址時,將發生共享。例如處理器A讀寫了主存的一個值,並裝入本身的高速緩存。處理器B也在隨後讀取了同一個值,此時會發生共享(或者叫內存爭用)。若是兩個處理器共享同一個主存地址,一個處理器修改了改地址的值,另外一個處理器的高速緩存中保存的值將會被做廢,以確保不會讀到過時值。這個問題就是緩存的一致性問題。

有一種最經常使用的叫作MESI的協議用於解決緩存一致性問題,下面來詳細瞭解一下。

首先對緩存塊的狀態進行命名:

  • modified (修改):緩存塊中的數據已經被修改,須要最終寫回到主存中去。其餘處理器不能緩存該數據。
  • exclusive(獨佔):緩存塊中的數據只被一個處理器未被修改。其餘處理器能夠緩存該數據。
  • shared(共享):緩存快中的數據被多個處理器共享且未被修改。其餘處理器能夠緩存該數據
  • invalid(無效):緩存塊中的數據無效。

咱們用例子來解釋一下MESI協議:

(1)剛開始,處理器A讀取主存中的數據a並儲存在高速緩存中。此時A的緩存塊對應的狀態是Exclusive。這個不用解釋。

(2)而後處理器B也讀取了相同的數據a也緩存到了本身的高速緩存中。此時A和B共享主存中的同一個數據a。因此它們的緩存塊狀態都是shared。


(3)接着,處理器B修改了數據a爲b,可是隻是修改了緩存塊中的數據,並無同步到主存中去。此時B的緩存塊狀態是modified,同時A的緩存塊狀態變爲了invalid。B在修改的同時迴向其餘處理器廣播,因此A修改了本身的緩存塊狀態。

(4)若是A此時要從讀取a時,會廣播請求。B收到請求將修改後的值同時發送給處理器A和主存,並將A和B的緩存塊狀態變成和諧的shared狀態。

其實這個MESI協議仍是比較好理解的。

六、自旋

若是處理器不斷的測試內存中的某個字,並等待另外一個處理器處理它,則稱該處理器正在自旋。舉例來講,內存中存在一個布爾變量a,初始值爲false。A處理器不斷地去讀取它,知道a被另外一個處理器設置成true爲止。咱們稱處理器A的行爲(不斷的讀取a,並測試a是否是爲true)叫作自旋。其實也就是名字唬人罷了。

對於沒有高速緩存的SMP系統結構來講,自旋是一種很是糟糕的想法。由於,每次自旋都是處理器到主存中去讀取值。每次的讀取操做都會消耗總線資源(還記得上面SMP的結構圖嗎),會直接影響到其餘處理器的推動。

對於無高速緩存的NUMA系統結構來講(NUMA也能夠帶高速緩存的)。若是自旋的地址位於本地存儲器中,這個是能夠接受的。不然也是糟糕的想法。還好目前不帶高速緩存的多處理器系統結構不多見。

對於有高速緩存的SMP和NUMA系統結構來講,自旋僅消耗很是少的資源。由於處理器第一次讀取肯會直接到主存中去讀(cache缺失),但後面自旋過程當中,只要數據沒有改變,處理器都會從本身的高速緩存中去讀取數據(cache命中)。這種咱們稱爲」本地自旋「。一旦高速緩存中的數據被改變,馬上會產生一個cache缺失(緩存塊狀態爲invalid),既然數據已經被改變,自旋也會隨之中止。

相關文章
相關標籤/搜索