原子操作

快看一隻熊發表於2016-01-13

  "原子操作(atomic operation)是不需要synchronized",這是Java多執行緒程式設計的老生常談了。

  所謂原子操作是指不會被執行緒排程機制打斷的操作;這種操作一旦開始,就一直執行到結束,中間不會有任何 context switch (cpu上下文切換)。

  定義:一個操作是原子的(atomic),如果這個操作所處的層(layer)的更高層不能發現其內部實現與結構。原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序是不可以被打亂,或者切割掉只執行部分。視作整體式原子性的核心。

  首先處理器會自動保證基本的記憶體操作的原子性。處理器保證從系統記憶體當中讀取或者寫入一個位元組是原子的,意思是當一個處理器讀取一個位元組時,其他處理器不能訪問這個位元組的記憶體地址,也就是lock住這塊記憶體,在我關於訊號量的隨筆中有提到《windows核心程式設計》,JVM同樣也是。

  我們以decl (遞減指令)為例,這是一個典型的"讀-改-寫"過程,涉及兩次記憶體訪問。設想在不同CPU執行的兩個程式都在遞減某個計數值,可能發生的情況是:

  ⒈ CPU A(CPU A上所執行的程式,以下同)從記憶體單元把當前計數值⑵裝載進它的暫存器中;
  ⒉ CPU B從記憶體單元把當前計數值⑵裝載進它的暫存器中。
  ⒊ CPU A在它的暫存器中將計數值遞減為1;
  ⒋ CPU B在它的暫存器中將計數值遞減為1;
  ⒌ CPU A把修改後的計數值⑴寫回記憶體單元。
  ⒍ CPU B把修改後的計數值⑴寫回記憶體單元。

  我們看到,記憶體裡的計數值應該是0,然而它卻是1。如果該計數值是一個共享資源的引用計數,每個程式都在遞減後把該值與0進行比較,從而確定是否需要釋放該共享資源。這時,兩個程式都去掉了對該共享資源的引用,但沒有一個程式能夠釋放它--兩個程式都推斷出:計數值是1,共享資源仍然在被使用。

  原子性不可能由軟體單獨保證--必須需要硬體的支援,因此是和架構相關的。在x86 平臺上,CPU提供了在指令執行期間對匯流排加鎖的手段。CPU晶片上有一條引線#HLOCK pin,如果組合語言的程式中在一條指令前面加上字首"LOCK",經過彙編以後的機器程式碼就使CPU在執行這條指令的時候把#HLOCK pin的電位拉低,持續到這條指令結束時放開,從而把匯流排鎖住,這樣同一匯流排上別的CPU就暫時不能通過匯流排訪問記憶體了,保證了這條指令在多處理器環境中的原子性。(我不是很懂)

  那麼,C#中,當我們對一個基礎值型別做改變的時候,等同於直接操作該值所存在的記憶體塊。所以值型別的變數複製值就是copy一個記憶體值去開闢一個新記憶體塊存進去。所以這裡由於硬體的原子性支援,這種操作即便是在多執行緒之下也是安全的。然而,int64型別在32位intel處理器上的賦值操作會因為相容性而被拆分成兩步,在多執行緒的情況下,兩步之間有可能被打斷,所以不是執行緒安全的。

  也有人會說靜態方法是否執行緒安全,其實,任何方法就像C裡面的函式一樣,都是執行緒安全的,只是一旦你在方法內部去宣告一些靜態變數或者用到全域性變數,執行緒同步問題你就必須考慮了。

 

相關文章