一:原子操作cas(compare-and-swap)
原子操作分三步:讀取addr的值,和old進行比較,如果相等,則將new賦值給*addr,他能保證這三步一起執行完成,叫原子操作也就是說它不能再分了,當有乙個cpu在訪問這塊內容addr時,其他cpu就不能訪問
)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
(fp)
ret(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併發排程原理學習
Linux原子操作,讀寫鎖機制
若干組合語言指令具有 讀 修改 寫 型別 也就是說,他們訪問儲存單元兩次,第一次讀原值,第二次寫新值。假定執行在兩個cpu上的兩個核心控制路徑試圖通過執行非原子操作來同時 讀 修改 寫 同乙個儲存器單元。首先,兩個cpu都試圖讀同乙個單元,但是儲存器仲裁器 對訪問ram晶元的操作進行序列化的硬體電路...
原子操作和鎖
原子操作 在多程序 執行緒 的作業系統中不能被其它程序 執行緒 打斷的操作就叫原子操作,檔案的原子操作是指操作檔案時的不能被打斷的操作。原子操作是不可分割的,在執行過程中不會被任何其它任務或事件中斷。linux核心提供了一系列函式來實現核心中的原子操作,這些函式又分為兩類,分別針對位和整型變數進行原...
mysql 原子鎖 Go語言檔案鎖操作
我們使用go語言開發一些程式的時候,往往出現多個程序同時操作同一份檔案的情況,這很容易導致檔案中的資料混亂。這時我們就需要採用一些手段來平衡這些衝突,檔案鎖 flock 應運而生,下面我們就來介紹一下。對於 flock,最常見的例子就是 nginx,程序執行起來後就會把當前的 pid 寫入這個檔案,...