Mutex 四大易错场景
1. Lock / Unlock 没有成对出现
func foo(){
var mu sync.Mutex
defer mu.Unlock()
fmt.Println("hello World!")
}
2. Copy 已经使用的Mutex
Mutex 是一个有状态的变量,复制了一个已经加锁的变量的话, 新的变量其实已经加锁了。
package main
import (
"fmt"
"sync"
)
type Counter struct {
sync.Mutex
Count int
}
func main() {
var c Counter
c.Lock()
defer c.Unlock()
c.Count++
foo(c) // 复制锁
}
// 这里Counter的参数是通过复制的方式传入的
func foo(c Counter) {
c.Lock()
defer c.Unlock()
fmt.Println("in foo")
}
运行之后报错
fatal error: all goroutines are asleep - deadlock!
也可以使用vet提前探测这个问题
go vet error2.go
# command-line-arguments
./error2.go:23:6: call of foo copies lock value: command-line-arguments.Counter
./error2.go:27:12: foo passes lock by value: command-line-arguments.Counter
3. 重入
可重入锁的概念是,当一个线程已经拥有了某把锁,当它再去请求这把锁的时候,不会阻塞,而是会成功返回。
Mutex是不可重入锁,在Mutex中没有记录拥有锁的协程的信息。
func foo(l sync.Locker) {
fmt.Println("in foo")
l.Lock()
bar(l)
l.Unlock()
}
func bar(l sync.Locker) {
l.Lock()
fmt.Println("in bar")
l.Unlock()
}
func main() {
l := &sync.Mutex{}
foo(l)
}
运行之后报错
fatal error: all goroutines are asleep - deadlock!
4. 死锁
4.1 死锁的条件
- 互斥
- 持有和等待
- 不可剥夺
- 环路等待
避免死锁,只需要破坏这四个条件中的一个或着几个。
4.2 死锁的例子
func main() {
var psCertificate sync.Mutex
var propertyCertificate sync.Mutex
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
psCertificate.Lock()
defer psCertificate.Unlock()
time.Sleep(5 * time.Second)
propertyCertificate.Lock()
propertyCertificate.Unlock()
}()
go func() {
defer wg.Done()
propertyCertificate.Lock()
defer propertyCertificate.Unlock()
time.Sleep( 5 * time.Second)
psCertificate.Lock()
psCertificate.Unlock()
}()
wg.Wait()
fmt.Println("处理完成")
}
运行报错
fatal error: all goroutines are asleep - deadlock!