工作中,經常遇到需要重試的場景,最簡單的方式可以用try...catch...加while迴圈來實現。那麼,有沒有統一的、優雅一點兒的處理方式呢?有的,Spring Retry就可以幫我們搞定重試問題。 關於重試,我們可以關註以下以下幾個方面: 什麼情況下去觸發重試機制 重試多少次,重試的時間間隔 ...
go讀寫鎖
互斥鎖每次只讓一 g
通過,去讀寫數據。但是讀數據操作,併發其實沒有問題。所以誕生了 讀寫鎖。
讀協程可以併發,一起讀。但是 寫協程還是要走互斥鎖,只能一個個通過。
先加了讀鎖
先加了讀鎖。那麼寫的協程,就需要去休眠隊列中等待。一直到讀鎖都釋放。
先加了寫鎖
這個時候,不管再來 寫協程還是讀協程,都去休眠隊列等待。
小結:
沒有加寫鎖時,多個協程都可以加讀鎖
加了寫鎖時,無法加讀鎖,讀協程排隊等待
加了讀鎖,寫鎖排隊等待
定義
type RWMutex struct {
w Mutex // held if there are pending writers
writerSem uint32 // semaphore for writers to wait for completing readers
readerSem uint32 // semaphore for readers to wait for completing writers
readerCount atomic.Int32 // number of pending readers
readerWait atomic.Int32 // number of departing readers
}
w:互斥鎖作為寫鎖
writerSem:作為寫協程隊列
readerSem:作為讀協程隊列
readerCount: 正值:正在讀的協程 負值:加了寫鎖
readerWait:寫鎖應該等待讀協程個數
有三個sema隊列了,w本身底層有一個,readerSem
和 writerSem
。
運行邏輯
當鎖是一個初始化的狀態,來了一個寫協程
`rwmutexMaxReaders` 是一個非常大的常量
把readerCount 改成了一個 很大的負數
加寫鎖,有讀協程已經在讀了
來了寫鎖後,把 readerCount
也減去了一個很大的數,但是 3還是能從這個值中體現。
但是,當再有 讀的g過來時候,發現readerCount
為負數,就會去readSem
中休眠。
代碼實現
讀鎖
加鎖
func (rw *RWMutex) RLock() {
// 將readerCount+1,發現還是負的,就去休眠,說明有寫鎖在等待
if rw.readerCount.Add(1) < 0 {
runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
}
// 如果不是負數,則加鎖成功,去讀數據
}
小結:
將給readerCount無腦加-
如果readerCount是正數,加鎖成功
如果readerCount是負數,說明被加了寫鎖,陷入`readerSem`
解鎖
func (rw *RWMutex) RUnlock() {
// 將 readerCount 減去1, 如果 r 小於0,說明有寫協程 在等待
if r := rw.readerCount.Add(-1); r < 0 {
// Outlined slow-path to allow the fast-path to be inlined
rw.rUnlockSlow(r)
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
// readerWait 寫鎖應該等待讀協程個數 減去1(自身) 之後,
//如果是 0,說明沒有 讀協程在讀取數據了 ,就去 `runtime_Semrelease `喚醒換一個寫協程
if rw.readerWait.Add(-1) == 0 {
// The last reader unblocks the writer.
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
讀鎖在解鎖時候,去判斷了 readerCount
是否小於0 是否有寫協程在等待;然後再釋放寫協程,又判斷了 readerWait
是否等於0,因為大於0,說明還有讀協程。
小結:
給readerCountit減1
如果readerCount是正數,解鎖成功,沒有寫協程在排隊
如果readerCount是負數,有寫鎖在排隊
如果自己是readerWait的最後一個,喚醒寫協程
問題: 但是讀鎖在加鎖時候,並沒有 給
readerWait
加值,這裡判斷是否有效呢 ?
寫鎖
加鎖
func (rw *RWMutex) Lock() {
rw.w.Lock() // 先去加互斥鎖,加上了再執行下麵的邏輯,加不上直接去 w的sema中休眠了
r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
// 判斷 r是否為0, 等於0 則直接加鎖成功了。
if r != 0 && rw.readerWait.Add(r) != 0 {
runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
}
}
講下這裡不為0的情況,如果r不為0,比如r為2,則說明還有2個讀協程在工作。
rw.readerWait.Add(r)
這句代碼,正好解釋了上面的問題。這裡給 readerWait
賦值了,所以讀協程在解鎖時候,判斷這個值才有用。
如果不來寫協程,那這個
readerWait
沒有意義,因為這是判斷是否釋放寫協程的。
那有沒有lock()
被兩個 寫協程 先後連續執行,讓 r
的值為一個很大的負數?
不會。因為要先去加 互斥鎖。一個寫協程加上後,其他的寫協程只能去 sema中等待。上篇有講過。 所以上面有一個舉例的圖其實是有問題的。
小結加寫鎖:
先加mutex寫鎖,若已經被加寫鎖會阻塞等待
將readerCount變為負值,阻塞讀鎖的獲取
計算需要等待多少個讀協程釋放
如果需要等待讀協程釋放,陷入writerSem
解寫鎖
func (rw *RWMutex) Unlock() {
r := rw.readerCount.Add(rwmutexMaxReaders)
// 去釋放讀協程,沒有去釋放 `writerSem`裡面的寫協程,因為這裡面根本不會有休眠的寫協程
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0) // 釋放讀協程
}
rw.w.Unlock() // 解鎖 互斥鎖,讓互斥鎖的sema等待隊列中的協程,重新去競爭鎖
}
小結 解寫鎖 :
將readercount變為正值,允許讀鎖的獲取
釋放在readerSem中等待的讀協程 上面講了,這個時候 writerSem 是空的
解鎖mutex
適合場景
適合讀多寫少的場景,如果都是寫的場景,其實和互斥鎖一樣。