golang 在多协程的情况下,如果多个协程同时操作一个变量,会出现数据不一致的情况,这个时候就需要使用互斥锁来解决这个问题。
互斥锁 (sync.Mutex)
互斥即不可同时运行。即使用了互斥锁的两个代码片段互相排斥,只有其中一个代码片段执行完成后,另一个才能执行。
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:
Lock 加锁
Unlock 释放锁
代码案例
下面是没有使用锁一个例子情况:
var setMap = make(map[int]bool,0)
func printOnce(num int) {
if _, exist := setMap[num]; !exist {
fmt.Println(num)
}
setMap[num] = true
}
func main() {
for i := 0; i < 10; i++ {
go printOnce(100)
}
time.Sleep(time.Second)
}
运行 go run main.go 会发生什么情况呢?
go run main.go
100
100
多运行几次打印不同的结果, 有时候打印7次,有时候打印10次,有时候还触发 panic,是因为对同一个数据结构的访问冲突了。解决的方法使用 Lock()
与 UnLock()
的方法解决问题
var m sync.Mutex
var setMap = make(map[int]bool, 0)
func printOnce(num int) {
m.Lock()
if _, exist := setMap[num]; !exist {
fmt.Println(num)
}
setMap[num] = true
m.Unlock()
}
func main() {
for i := 0; i < 10; i++ {
go printOnce(100)
}
time.Sleep(time.Second)
}
运行 go run main.go
go run main.go
100
注意:
一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 就算调用 Lock() 方法也会等待锁的释放。
func main() {
ch := make(chan struct{}, 2)
var m sync.Mutex
go func() {
m.Lock()
defer m.Unlock()
fmt.Println("goroutine1: 大概锁定 2s")
time.Sleep(time.Second * 2)
fmt.Println("goroutine1: 锁定结束,准备退出")
ch <- struct{}{}
}()
go func() {
fmt.Println("goroutine2: 等待解锁")
m.Lock()
defer m.Unlock()
fmt.Println("goroutine2: 锁定结束")
ch <- struct{}{}
}()
// 等待 goroutine 执行结束
for i := 0; i < 2; i++ {
<-ch
}
}
运行 go run main.go
go run main.go
goroutine1: 大概锁定 2s
goroutine2: 等待解锁
goroutine1: 锁定结束,准备退出
goroutine2: 锁定结束
sync.Map (Go 1.9+)
Go 1.9 标准库提供了内置并发安全的 map,可以安全地被多个 goroutine 并发使用,无需额外的加锁或协调。
import "sync"
var sm sync.Map // 直接声明即可使用
// 存储
sm.Store("key1",100)
// 读取
if value ,ok := sm.Load("key1");ok {
fmt.PrintLn("Loaded value",value.(int)) // 注意类型断言
}
// 读取或存储
actual, loaded := sm.LoadOrStore("key2",200)
fmt.Println("LoadOrStore",actual.(int),"loaded?",loaded)
// 删除
sm.Delete("key1")
// 遍历(注意 Range 的函数签名)
sm.Range(func(key , value any) bool{
fmt.Println("Range:",key.(string),value.(int))
return true // 返回 true 继续遍历 , false 停止
})