原子操作&普通鎖&讀寫鎖


一:原子操作CAS(compare-and-swap)
原子操作分三步:讀取addr的值,和old進行比較,如果相等,則將new賦值給*addr,他能保證這三步一起執行完成,叫原子操作也就是說它不能再分了,當有一個CPU在訪問這塊內容addr時,其他CPU就不能訪問
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
TEXT ·CompareAndSwapUint64(SB),NOSPLIT,$0-25
   MOVD   addr+0(FP), R3
   MOVD   old+8(FP), R4
   MOVD   new+16(FP), R5
   SYNC
   LDAR   (R3), R6
   CMP    R6, R4
   BNE    7(PC)
   STDCCC R5, (R3)
   BNE    -4(PC)
   ISYNC
   MOVD   $1, R3
   MOVB   R3, swapped+24(FP)
   RET
   MOVB   R0, swapped+24(FP)
    RET
 
 
二:普通鎖
加鎖(Mutex.Lock)
1:原子操作加鎖:原子操作判斷是否已經被加鎖,如果沒有加鎖,原子操作加鎖,直接返回,很快嗎!
2:執行旋轉鎖:已經被加鎖,判斷是否可以執行旋轉鎖,執行旋轉鎖,原子判斷是否可以加鎖,若可以,加鎖返回
3:當前G休眠等待被喚醒:在執行旋轉鎖期間,鎖還是沒釋放,那就只能讓當前協程休眠,等待被喚醒,當鎖被釋放后,當前G被喚醒繼續執行
 
釋放鎖(Mutex.UnLock)
1:將加鎖狀態去掉,判斷是否有等待的協程,如沒有直接返回
2:若有等待協程,將狀態設置成喚醒狀態
3:喚醒一個等待協程
 
 
三:讀寫鎖
讀寫鎖基於普通鎖實現
加寫鎖(RWMutex.Lock)
1:加普通鎖
2:改讀鎖的數量readerCount -= 1 << 30
3:如果有正在讀的鎖,等待直到讀鎖完成,讀寫不能同時進行
 
釋放寫鎖(RWMutex.UnLock)
1:改讀鎖的數量readerCount += 1 << 30,加鎖的時候減了這么多,釋放鎖的時候加回來
2:如果readerCount>=  1 << 30,拋異常,釋放沒有加鎖的鎖
3:喚醒所有正在等待讀的協程
4:釋放普通鎖
 
加讀鎖(RWMutex.RLock)
1:原子操作讀鎖數量加1,readerCount+=1
2:如果rederCount<0,說明有寫功能正在執行,協程進入睡眠狀態,等待寫完之后被喚醒
3:如果沒有正在執行的寫鎖,就完事了,整個加鎖操作就只執行了一個原子操作,還是很快的
 
釋放讀鎖(RWMutex.RUnLock)
1:原子操作讀鎖數量減1
2:如果讀鎖數量==-1,或==-1 << 30,說明釋放了一個沒有加讀鎖的鎖,或者釋放了一個正在寫的鎖,直接報錯
3:如果有正在等待的寫鎖,喚醒它,否則整個釋放讀鎖也就執行了一個原子操作
 
所以說,鎖是基於原子操作的,原子操作保證了數據的一致性,讀寫鎖基於普通鎖來實現,對於一個寫少讀多的程序來說,讀寫鎖會比普通鎖快很多
 
加鎖原理
1:先是CAS的方式嘗試獲取鎖,如果獲取到了,就鎖住,並繼續執行被鎖住的代碼,然后在釋放鎖
2:CAS沒有拿到鎖,就只能等待了,比如有10個協程(G)在等這個待鎖,go並不是一把鎖創建一個隊列,而是默認創建251個隊列,通過hash的方式將G加入隊列,確保等待同一把鎖的G在同一個隊列,然后將當前G執行上下文信息保存到G.sched,下次就可以繼續從這里執行,這樣這個等待的G就這樣被扔到隊列中了,而不是將這個G狀態改成等待狀態等待被喚醒,G去睡覺了,P還得繼續執行,於是會找一個P,繼續執行
 
解鎖原理
1:通過鎖定位到對應的隊列,所有等待這把鎖的G都在這個隊列中,查找是否有等待的G,沒有就返回
2:有就將G狀態改成可運行,並加入到運行隊列,等待被調度
 
關於G調度請看我的這篇文章: go並發調度原理學習
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM