前面已經講過很多Golang系列知識,感興趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863.html,
接下來要說的是golang的鎖的使用場景主要涉及到哪些?讀寫鎖為什么會比普通鎖快。
一、什么場景下需要用到鎖
當程序中就一個線程的時候,是不需要加鎖的,但是通常實際的代碼不會只是單線程,有可能是多個線程同時訪問公共資源,所以這個時候就需要用到鎖了,那么關於鎖的使用場景主要涉及到哪些呢?
1. 多個線程在讀相同的數據時
2. 多個線程在寫相同的數據時
3. 同一個資源,有讀又有寫時
二、Go中鎖分為兩種:
- 互斥鎖 (sync.Mutex)
- 讀寫鎖 (sync.RWMutex 底層依賴Mutex實現 )
互斥鎖是並發程序對公共資源訪問限制最常見的方式。在Go中,sync.Mutex 提供了互斥鎖的實現。
當一個goroutine獲得了Mutex后,其他goroutine只能等待,除非該goroutine釋放這個Mutex。
互斥鎖結構:
type Mutex struct {
state int32
sema uint32
}
1. 鎖定狀態值為1,未鎖定狀態鎖未0 。
2. Lock()加鎖、Unlock解鎖。
讀寫鎖則是對讀寫操作進行加鎖。需要注意的是多個讀操作之間不存在互斥關系,這樣提高了對共享資源的訪問效率。
Go中讀寫鎖由 sync.RWMutex 提供,RWMutex在讀鎖占用的情況下,會阻止寫,但不阻止讀。RWMutex在寫鎖占用情況下,會阻止任何其他goroutine(無論讀和寫)進來,整個鎖相當於由該goroutine獨占。
讀寫鎖結構:
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 int32 // number of pending readers readerWait int32 // number of departing readers }
1. RWMutex是單寫多讀鎖,該鎖可以加多個讀鎖或者一個寫鎖。
2. 讀鎖占用的情況會阻止寫,不會阻止讀,多個goroutine可以同時獲取讀鎖。
3. 寫鎖會阻止其他gorotine不論讀或者寫進來,整個鎖由寫鎖goroutine占用 與第一條共用示范代碼
4. 適用於讀多寫少的場景
三、如何使用互斥鎖
Mutex為互斥鎖,Lock() 加鎖,Unlock() 解鎖,使用Lock() 加鎖后,便不能再次對其進行加鎖,直到利用Unlock()解鎖對其解鎖后,才能再次加鎖.適用於讀寫不確定場景,即讀寫次數沒有明顯的區別,並且只允許只有一個讀或者寫的場景,所以該鎖葉叫做全局鎖。
互斥鎖只能鎖定一次,當在解鎖之前再次進行加鎖,便會無法加鎖。如果在加鎖前解鎖,便會報錯"panic: sync: unlock of unlocked mutex"。
package main import ("fmt" "sync" ) var ( count int lock sync.Mutex ) func main() { for i := 0; i < 2; i++ { go func() { for i := 1000000; i > 0; i-- { lock.Lock() count ++ lock.Unlock() } fmt.Println(count) }() } fmt.Scanf("\n") //等待子線程全部結束 } 運行結果: 1952533 2000000 //最后的線程打印輸出
對於上面的程序,a作為一個公共的資源,所以對a的改變、讀寫等操作都需要加鎖。
需要注意的問題:
1. 不要重復鎖定互斥鎖
2. 不要忘記解鎖互斥鎖,必要時使用 defer 語句
3. 不要在多個函數之間直接傳遞互斥鎖
四、如何使用讀寫鎖
讀寫鎖的場景主要是在多線程的安全操作下,並且讀的情況多於寫的情況,也就是說既滿足多線程操作的安全性,也要確保性能不能太差,這時候,我們可以考慮使用讀寫鎖。當然你也可以簡單暴力直接使用互斥鎖(Mutex)。
Lock() 寫鎖,如果在添加寫鎖之前已經有其他的讀鎖和寫鎖,則lock就會阻塞直到該鎖可用,為確保該鎖最終可用,已阻塞的 Lock 調用會從獲得的鎖中排除新的讀取器,即寫鎖權限高於讀鎖,有寫鎖時優先進行寫鎖定。
Unlock() 寫鎖解鎖,如果沒有進行寫鎖定,則就會引起一個運行時錯誤。
RLock() 讀鎖,當有寫鎖時,無法加載讀鎖,當只有讀鎖或者沒有鎖時,可以加載讀鎖,讀鎖可以加載多個,所以適用於"讀多寫少"的場景。
RUnlock() 讀鎖解鎖,RUnlock 撤銷單次RLock 調用,它對於其它同時存在的讀取器則沒有效果。若 rw 並沒有為讀取而鎖定,調用 RUnlock 就會引發一個運行時錯誤。
package main import ("fmt" "sync" ) var ( count int rwLock sync.RWMutex ) func main() { for i := 0; i < 2; i++ { go func() { for i := 1000000; i > 0; i-- { rwLock.Lock() count ++ rwLock.Unlock() } fmt.Println(count) }() } fmt.Scanf("\n") //等待子線程全部結束 } 運行結果: 1968637 2000000
看着挺復雜的,其實簡單來說就是:
-
讀鎖不能阻塞讀鎖
-
讀鎖需要阻塞寫鎖,直到所有讀鎖都釋放
-
寫鎖需要阻塞讀鎖,直到所有寫鎖都釋放
-
寫鎖需要阻塞寫鎖
五、最后
以上,就把golang中各種鎖的使用場景及怎么使用互斥鎖和讀寫鎖等相關內容介紹完了,希望能對大家有所幫助。