Golang中的sync包實現了兩種鎖:互斥鎖(Mutex)和讀寫鎖(RWMutex)。
互斥鎖(sync.Mutex)
- 使用Lock方法加鎖,使用Unlock方法解鎖,Golang從1.18新增了TryLock方法,用于嘗試獲取鎖,返回成功或者失敗;
- 如果Mutex被一個goroutine獲取后,其他goroutine只能等到這個goroutine釋放該Mutex后才能獲取;
- 使用Lock方法加鎖后,不能再次加鎖,直到使用Unlock方法釋放鎖后才能再次加鎖;加鎖之前使用Unlock方法會導致panic;
- Mutex并不與特定的goroutine相關聯,一個goroutine加鎖,也可以使用另一個goroutine解鎖;
- 在同一個goroutine中的Mutex解鎖之前再次進行加鎖,會導致死鎖。
互斥鎖的一個經典案例是對切片進行并發安全的操作,代碼如下:
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var slice []int
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
for j := 0; j < 100; j++ {
mu.Lock()
slice = Append(slice, j)
mu.Unlock()
}
wg.Done()
}()
}
wg.Wait()
fmt.Println(len(slice))
}
首先定義了一個長度為0的slice,并發地對其進行了1000次append操作。
讀寫鎖(RWMutex)
- RWMutex該鎖可以加多個讀鎖(RLock)或者一個寫鎖(Lock),Golang從1.18新增了TryLock和TryRLock方法,分別用于嘗試獲取寫鎖和讀鎖,返回成功或者失敗;
- 讀鎖被占用的情況下會阻止寫,不會阻止讀,多個goroutine可以同時獲得讀鎖;
- 寫鎖會阻止其他goroutine對寫鎖和讀鎖的獲取。
看一個代碼示例:
package main
import (
"log"
"sync"
)
var mu sync.RWMutex
var m map[string]string
func main() {
m = make(map[string]string)
mu.Lock()
m["a"] = "A"
mu.Unlock()
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
mu.RLock()
log.Println(m["a"]) // 并發讀
mu.RUnlock()
wg.Done()
}(i)
}
wg.Wait()
}
首先定義了一個map,然后對其進行了一次寫操作和10次并發讀操作。在并發讀的時候,使用讀鎖來支持并發訪問。
小結
本文講解了互斥鎖和讀寫鎖的特點,兩種鎖都可以用于協調多個并發訪問共享資源的同步機制。可以看出互斥鎖適用于讀寫不確定,并且只有一個讀或者寫的場景;讀寫鎖適用于讀多寫少的場景,可以有更高的性能。需要注意的是盡量避免在鎖的范圍內進行耗時較長的操作,以免影響性能。