您好,登錄后才能下訂單哦!
本篇內容主要講解“Golang中的RWMutex怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Golang中的RWMutex怎么使用”吧!
正如 RWMutex
的命名那樣,它是區分了讀鎖和寫鎖的鎖,所以我們可以從讀和寫兩個方面來看 RWMutex
的模型。
下文中的 reader
指的是進行讀操作的 goroutine,writer
指的是進行寫操作的 goroutine。
我們可以用下圖來表示 RWMutex
的讀操作模型:
上圖使用了 w.Lock
,是因為 RWMutex
的實現中,寫鎖是使用 Mutex
來實現的。
說明:
讀操作的時候可以同時有多個 goroutine 持有 RLock
,然后進入臨界區。(也就是可以并行讀),上圖的 G1
、G2
和 G3
就是同時持有 RLock
的幾個 goroutine。
在讀操作的時候,如果有 goroutine 持有 RLock
,那么其他 goroutine (不管是讀還是寫)就只能等待,直到所有持有 RLock
的 goroutine 釋放鎖。
也就是上圖的 G4
需要等待 G1
、G2
和 G3
釋放鎖之后才能進入臨界區。
最后,因為 G5
和 G6
這兩個協程獲取鎖的時機比 G4
晚,所以它們會在 G4
釋放鎖之后才能進入臨界區。
我們可以用下圖來表示 RWMutex
的寫操作模型:
說明:
寫操作的時候只能有一個 goroutine 持有 Lock
,然后進入臨界區,釋放寫鎖之前,所有其他的 goroutine 都只能等待。
上圖的 G1
~G5
表示的是按時間順序先后獲取鎖的幾個 goroutine。
上面幾個 goroutine 獲取鎖的過程是:
G1
獲取寫鎖,進入臨界區。然后 G2
、G3
、G4
和 G5
都在等待。
G1
釋放寫鎖之后,G2
和 G3
可以同時獲取讀鎖,進入臨界區。然后 G3
、G4
和 G5
都在等待。
G2
和 G3
可以同時獲取讀鎖,進入臨界區。然后 G4
和 G5
都在等待。
G2
和 G3
釋放讀鎖之后,G4
獲取寫鎖,進入臨界區。然后 G5
在等待。
最后,G4
釋放寫鎖,G5
獲取讀鎖,進入臨界區。
RWMutex
中包含了以下的方法:
Lock
:獲取寫鎖,如果有其他 goroutine 持有讀鎖或寫鎖,那么就會阻塞等待。
Unlock
:釋放寫鎖。
RLock
:獲取讀鎖,如果有其他 goroutine 持有寫鎖,那么就會阻塞等待。
RUnlock
:釋放讀鎖。
其他不常用的方法:
RLocker
:返回一個讀鎖,該鎖包含了 RLock
和 RUnlock
方法,可以用來獲取讀鎖和釋放讀鎖。
TryLock
: 嘗試獲取寫鎖,如果獲取成功,返回 true
,否則返回 false
。不會阻塞等待。
TryRLock
: 嘗試獲取讀鎖,如果獲取成功,返回 true
,否則返回 false
。不會阻塞等待。
我們可以通過下面的例子來看一下 RWMutex
的基本用法:
package mutex import ( "sync" "testing" ) var config map[string]string var mu sync.RWMutex func TestRWMutex(t *testing.T) { config = make(map[string]string) // 啟動 10 個 goroutine 來寫 var wg1 sync.WaitGroup wg1.Add(10) for i := 0; i < 10; i++ { go func() { set("foo", "bar") wg1.Done() }() } // 啟動 100 個 goroutine 來讀 var wg2 sync.WaitGroup wg2.Add(100) for i := 0; i < 100; i++ { go func() { get("foo") wg2.Done() }() } wg1.Wait() wg2.Wait() } // 獲取配置 func get(key string) string { // 獲取讀鎖,可以多個 goroutine 并發讀取 mu.RLock() defer mu.RUnlock() if v, ok := config[key]; ok { return v } return "" } // 設置配置 func set(key, val string) { // 獲取寫鎖 mu.Lock() defer mu.Unlock() config[key] = val }
上面的例子中,我們啟動了 10 個 goroutine 來寫配置,啟動了 100 個 goroutine 來讀配置。 這跟我們現實開發中的場景是一樣的,很多時候其實是讀多寫少的。 如果我們在讀的時候也使用互斥鎖,那么就會導致讀的性能非常差,因為讀操作一般都不會有副作用的,但是如果使用互斥鎖,那么就只能一個一個的讀了。
而如果我們使用 RWMutex
,那么就可以同時有多個 goroutine 來讀取配置,這樣就可以大大提高讀的性能。 因為我們進行讀操作的時候,可以多個 goroutine 并發讀取,這樣就可以大大提高讀的性能。
在《深入理解 go Mutex》中,我們已經講過了 Mutex
的使用注意事項, 其實 RWMutex
的使用注意事項也是差不多的:
不要忘記釋放鎖,不管是讀鎖還是寫鎖。
Lock
之后,沒有釋放鎖之前,不能再次使用 Lock
。
在 Unlock
之前,必須已經調用了 Lock
,否則會 panic
在第一次使用 RWMutex
之后,不能復制,因為這樣一來 RWMutex
的狀態也會被復制。這個可以使用 go vet
來檢查。
RWMutex
的一些實現原理跟 Mutex
是一樣的,比如阻塞的時候使用信號量等,在 Mutex
那一篇中已經有講解了,這里不再贅述。 這里就 RWMutex
的實現原理進行一些簡單的剖析。
RWMutex
的結構體定義如下:
type RWMutex struct { w Mutex // 互斥鎖,用于保護讀寫鎖的狀態 writerSem uint32 // writer 信號量 readerSem uint32 // reader 信號量 readerCount atomic.Int32 // 所有 reader 數量 readerWait atomic.Int32 // writer 等待完成的 reader 數量 }
各字段含義:
w
:互斥鎖,用于保護讀寫鎖的狀態。RWMutex
的寫鎖是互斥鎖,所以直接使用 Mutex
就可以了。
writerSem
:writer 信號量,用于實現寫鎖的阻塞等待。
readerSem
:reader 信號量,用于實現讀鎖的阻塞等待。
readerCount
:所有 reader 數量(包括已經獲取讀鎖的和正在等待獲取讀鎖的 reader)。
readerWait
:writer 等待完成的 reader 數量(也就是獲取寫鎖的時刻,已經獲取到讀鎖的 reader 數量)。
因為要區分讀鎖和寫鎖,所以在 RWMutex
中,我們需要兩個信號量,一個用于實現寫鎖的阻塞等待,一個用于實現讀鎖的阻塞等待。 我們需要特別注意的是 readerCount
和 readerWait
這兩個字段,我們可能會比較好奇,為什么有了 readerCount
這個字段, 還需要 readerWait
這個字段呢?
這是因為,我們在嘗試獲取寫鎖的時候,可能會有多個 reader 正在使用讀鎖,這時候我們需要知道有多少個 reader 正在使用讀鎖, 等待這些 reader 釋放讀鎖之后,就獲取寫鎖了,而 readerWait
這個字段就是用來記錄這個數量的。 在 Lock
中獲取寫鎖的時候,如果觀測到 readerWait
不為 0 則會阻塞等待,直到 readerWait
為 0 之后才會真正獲取寫鎖,然后才可以進行寫操作。
獲取讀鎖的方法如下:
// 獲取讀鎖 func (rw *RWMutex) RLock() { if rw.readerCount.Add(1) < 0 { // 有 writer 在使用鎖,阻塞等待 writer 完成 runtime_SemacquireRWMutexR(&rw.readerSem, false, 0) } }
讀鎖的實現很簡單,先將 readerCount
加 1,如果加 1 之后的值小于 0,說明有 writer 正在使用鎖,那么就需要阻塞等待 writer 完成。
釋放讀鎖的方法如下:
// 釋放讀鎖 func (rw *RWMutex) RUnlock() { // readerCount 減 1,如果 readerCount 小于 0 說明有 writer 在等待 if r := rw.readerCount.Add(-1); r < 0 { // 有 writer 在等待,喚醒 writer rw.rUnlockSlow(r) } } // 喚醒 writer func (rw *RWMutex) rUnlockSlow(r int32) { // 未 Lock 就 Unlock,panic if r+1 == 0 || r+1 == -rwmutexMaxReaders { fatal("sync: RUnlock of unlocked RWMutex") } // readerWait 減 1,返回值是新的 readerWait 值 if rw.readerWait.Add(-1) == 0 { // 最后一個 reader 喚醒 writer runtime_Semrelease(&rw.writerSem, false, 1) } }
讀鎖的實現總結:
獲取讀鎖的時候,會將 readerCount
加 1
如果正在獲取讀鎖的時候,發現 readerCount
小于 0,說明有 writer 正在使用鎖,那么就需要阻塞等待 writer 完成。
釋放讀鎖的時候,會將 readerCount
減 1
如果 readerCount
減 1 之后小于 0,說明有 writer 正在等待,那么就需要喚醒 writer。
喚醒 writer 的時候,會將 readerWait
減 1,如果 readerWait
減 1 之后為 0,說明 writer 獲取鎖的時候存在的 reader 都已經釋放了讀鎖,可以獲取寫鎖了。
·rwmutexMaxReaders算是一個特殊的標識,在獲取寫鎖的時候會將
readerCount的值減去
rwmutexMaxReaders, 所以在其他地方可以根據
readerCount` 是否小于 0 來判斷是否有 writer 正在使用鎖。
獲取寫鎖的方法如下:
// 獲取寫鎖 func (rw *RWMutex) Lock() { // 首先,解決與其他寫入者的競爭。 rw.w.Lock() // 向讀者宣布有一個待處理的寫入。 // r 就是當前還沒有完成的讀操作,等這部分讀操作完成之后才可以獲取寫鎖。 r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders // 等待活躍的 reader if r != 0 && rw.readerWait.Add(r) != 0 { // 阻塞,等待最后一個 reader 喚醒 runtime_SemacquireRWMutex(&rw.writerSem, false, 0) } }
釋放寫鎖的方法如下:
// 釋放寫鎖 func (rw *RWMutex) Unlock() { // 向 readers 宣布沒有活動的 writer。 r := rw.readerCount.Add(rwmutexMaxReaders) if r >= rwmutexMaxReaders { // r >= 0 并且 < rwmutexMaxReaders 才是正常的(r 是持有寫鎖期間嘗試獲取讀鎖的 reader 數量) fatal("sync: Unlock of unlocked RWMutex") } // 如果有 reader 在等待寫鎖釋放,那么喚醒這些 reader。 for i := 0; i < int(r); i++ { runtime_Semrelease(&rw.readerSem, false, 0) } // 允許其他的 writer 繼續進行。 rw.w.Unlock() }
寫鎖的實現總結:
獲取寫鎖的時候,會將 readerCount
減去 rwmutexMaxReaders
,這樣就可以區分讀鎖和寫鎖了。
如果 readerCount
減去 rwmutexMaxReaders
之后不為 0,說明有 reader 正在使用讀鎖,那么就需要阻塞等待這些 reader 釋放讀鎖。
釋放寫鎖的時候,會將 readerCount
加上 rwmutexMaxReaders
。
如果 readerCount
加上 rwmutexMaxReaders
之后大于 0,說明有 reader 正在等待寫鎖釋放,那么就需要喚醒這些 reader。
TryRLock
和 TryLock
的實現都很簡單,都是嘗試獲取讀鎖或者寫鎖,如果獲取不到就返回 false
,獲取到了就返回 true
,這兩個方法不會阻塞等待。
// TryRLock 嘗試鎖定 rw 以進行讀取,并報告是否成功。 func (rw *RWMutex) TryRLock() bool { for { c := rw.readerCount.Load() // 有 goroutine 持有寫鎖 if c < 0 { return false } // 嘗試獲取讀鎖 if rw.readerCount.CompareAndSwap(c, c+1) { return true } } } // TryLock 嘗試鎖定 rw 以進行寫入,并報告是否成功。 func (rw *RWMutex) TryLock() bool { // 寫鎖被占用 if !rw.w.TryLock() { return false } // 讀鎖被占用 if !rw.readerCount.CompareAndSwap(0, -rwmutexMaxReaders) { // 釋放寫鎖 rw.w.Unlock() return false } // 成功獲取到鎖 return true }
到此,相信大家對“Golang中的RWMutex怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。