本文轉載自聊聊CPU的LOCK指令
導語
在多線程操作中,可能最經常被提起的就是數據的可見性、原子性、有序性。不管是硬件方面、軟件方面都在這三方面做了很足的工作,才能保證程序的正常運行。
之前發表過一篇文章聊聊緩存一致性協議 如果感興趣的話可以去閱讀一下,里面談到了緩存一致性的實現和處理過程,讀完之后可以仔細去細想一下緩存一致性協議
到底解決了什么問題。個人理解緩存一致性協議
解決了CPU層面的可見性和一致性問題,閱讀到這里可以在這里停下來,仔細回想一下緩存一致性的原理,它通過監聽共享總線上消息,對自己緩存中的數據修改不同的狀態,來保證數據的一致性,對自己緩存中的數據失效后,下次讀取會從主存中直接讀取最新的數據 ,可以保證可見性,同時保證各緩存中的數據是一致的。
軟件的並發編程一樣,其實除了可見性、有序性,在計算機指令在執行的過程中,CPU通過不停地切換線程執行,給每個線程分配CPU時間片來實現多線程機制,一定也會存在原子性問題,在計算機層面是怎么解決原子性問題的,這就我們今天要聊的LOCK#指令,有時也被我們稱為總線鎖。
LOCK指令作用
在Intel® 64 and IA-32 Architectures Software Developer’s Manual 中的章節LOCK—Assert LOCK# Signal Prefix 中給出LOCK指令的詳細解釋
大至翻譯之后的意思如下
在CPU的LOCK信號被聲明之后,在此期隨同執行的指令會轉換成原子指令。在多處理器環境中,LOCK信號確保,在此信號被聲明之后,處理器獨占使用任何共享內存。
在不大多數IA-32和Inter64位處理器中,鎖可能在沒有LOCK#信號的時情況下發生。請參閱下面的“IA32體系結構兼容性”部分的詳細內容。
LOCK前綴只能預加在以下指令前面,並且只能加在這些形式的指令前面,其中目標操作數是內存操作數:
add、adc、and、btc、btr、bts、cmpxchg、cmpxch8b,cmpxchg16b,dec,inc,neg,not,or,sbb,sub,xor,xadd
和xchg
。如果LOCK前綴用上述列表中的指令並且源操作數是內存操作數(也就是指令沒有對內存進行寫操作),可能會出現未定義的
操作碼異常
(ud)。如果鎖前綴與任何不在上面列表中的指令一起使用,也將生成未定義的操作碼異常。
xchg
指令不管有沒有聲明LOCK前綴,總是會聲明LOCK信號。鎖定前綴通常與BTS指令一起使用,在共享內存環境中,以對內存地址執行
讀-修改-寫
操作。鎖定前綴的完整性不受內存字段對齊的影響。對於任意未對齊的字段,可以觀察到內存鎖定。
此指令的操作在非64位模式和64位模式下是相同的。
從P6系列處理器開始,當使用 LOCK 指令訪問的內存已經被處理器加載到緩存中時,LOCK# 信號通常不會斷言。取而代之的是,只鎖定處理器的緩存。在這里處理器的緩存一致性機制確保對內存進行的操作是原子性的。請參見“鎖定操作對內部處理器緩存的影響”,在Intel®64和IA-32體系結構軟件開發人員手冊第3A卷第8章中,有關鎖定緩存的詳細信息。
大致翻譯差不多如上,核心意思主要說明LOCK指令在聲明之后通過鎖定總線,獨占共享內存,通過一種排它的思想確保當前對內存操作的只有一個線程,然后確定在這段聲明期間指令執行不會被打斷,來保證其原子性。
處理器如何實現原子操作
首先處理器會保證基本的內存操作的原子性,比如從內存讀取或者寫入一個字節是原子的,但對於讀-改-寫
、或者是其它復雜的內存操作是不能保證其原子性的,又比如跨總線寬度
、跨多個緩存行
和誇頁表的訪問
,這時候需要處理器提供總線鎖
和緩存鎖
兩個機制來保證復雜的內存操作原子性
總線鎖
LOCK#信號就是我們經常說到的總線鎖,處理器使用LOCK#信號達到鎖定總線,來解決原子性問題,當一個處理器往總線上輸出LOCK#信號時,其它處理器的請求將被阻塞,此時該處理器此時獨占共享內存。
總線鎖這種做法鎖定的范圍太大了,導致CPU利用率急劇下降,因為使用LOCK#是把CPU和內存之間的通信鎖住了,這使得鎖定時期間,其它處理器不能操作其內存地址的數據 ,所以總線鎖的開銷比較大。
緩存鎖
如果訪問的內存區域已經緩存在處理器的緩存行中,P6系統和之后系列的處理器則不會聲明LOCK#信號,它會對CPU的緩存中的緩存行進行鎖定,在鎖定期間,其它 CPU 不能同時緩存此數據,在修改之后,通過緩存一致性
協議來保證修改的原子性,這個操作被稱為“緩存鎖”
什么情況下使用總線鎖(LOCK#)
當操作的數據不能被緩存在處理器內部,或操作的數據跨多個緩存行時,也會使用總線鎖
因為從P6系列處理器開始才有緩存鎖,所以對於早些處理器是不支持緩存鎖定的,也會使用總線鎖
有些指令自帶總線鎖
BTS、BTR、BTC 、XADD、CMPXCHG、ADD、OR
等,這些指令操作的內存區域就會加鎖,導致其它處理器不能同時訪問它。
在上面指令中的CMPXCHG
就是JAVA里面CAS
底層常用的指令,這個指令在執行的時候,會自動加總線鎖保,導致其它 處理器不能同時訪問,證其原子性。
LOCK#作用總結
- 鎖總線,其它CPU對內存的讀寫請求都會被阻塞,直到鎖釋放,因為鎖總線的開銷比較大,后來的處理器都采用鎖緩存替代鎖總線,在無法使用緩存鎖的時候會降級使用總線鎖
- lock期間的寫操作會回寫已修改的數據到主內存,同時通過緩存一致性協議讓其它CPU相關緩存行失效
寫在最后
總線鎖
、緩存鎖
可以保證原子性
,緩存一致性協議
可以保證可見性
,那么JAVA中的內存模型,它做了些什么?下一篇聊聊JAVA中的內存模型(JMM)聊聊JMM