一:原子操做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狀態改爲可運行,並加入到運行隊列,等待被調度