可能导致Mutex死锁的常见场景
- 重复锁定:
- 同一个goroutine对已经锁定的Mutex再次进行锁定。例如:
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
mu.Lock()
mu.Lock() // 这里会导致死锁,因为已经锁定的Mutex再次被锁定
defer mu.Unlock()
fmt.Println("This code will never be reached.")
}
- 未解锁:
- goroutine获取锁后,由于程序异常或逻辑问题没有执行解锁操作。例如:
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
mu.Lock()
// 假设这里发生了一个未处理的panic
panic("Something went wrong")
mu.Unlock() // 这行代码永远不会执行,导致死锁
fmt.Println("This code will never be reached.")
}
- 循环依赖:
- 多个goroutine相互等待对方释放锁,形成循环依赖。例如:
package main
import (
"fmt"
"sync"
)
var mu1 sync.Mutex
var mu2 sync.Mutex
func goroutine1() {
mu1.Lock()
defer mu1.Unlock()
fmt.Println("goroutine1 locked mu1")
mu2.Lock()
defer mu2.Unlock()
fmt.Println("goroutine1 locked mu2")
}
func goroutine2() {
mu2.Lock()
defer mu2.Unlock()
fmt.Println("goroutine2 locked mu2")
mu1.Lock()
defer mu1.Unlock()
fmt.Println("goroutine2 locked mu1")
}
func main() {
go goroutine1()
go goroutine2()
select {}
}
- 在上述例子中,
goroutine1
先锁定mu1
,然后尝试锁定mu2
,而goroutine2
先锁定mu2
,然后尝试锁定mu1
,形成了循环依赖,导致死锁。
避免死锁的方法
- 确保解锁:
- 使用
defer
语句确保在函数返回时解锁Mutex,无论函数执行过程中是否发生错误或异常。例如:
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
// 这里即使发生异常,Mutex也会被解锁
fmt.Println("Mutex is locked and will be unlocked.")
}
- 避免重复锁定:
- 仔细设计代码逻辑,确保同一个goroutine不会对已经锁定的Mutex再次进行锁定。
- 避免循环依赖:
- 对锁的获取顺序进行统一规划,避免形成循环依赖。例如,在多个锁的情况下,所有goroutine都按照相同的顺序获取锁。对于上面的循环依赖例子,可以修改如下:
package main
import (
"fmt"
"sync"
)
var mu1 sync.Mutex
var mu2 sync.Mutex
func goroutine1() {
mu1.Lock()
defer mu1.Unlock()
fmt.Println("goroutine1 locked mu1")
mu2.Lock()
defer mu2.Unlock()
fmt.Println("goroutine1 locked mu2")
}
func goroutine2() {
mu1.Lock()
defer mu1.Unlock()
fmt.Println("goroutine2 locked mu1")
mu2.Lock()
defer mu2.Unlock()
fmt.Println("goroutine2 locked mu2")
}
func main() {
go goroutine1()
go goroutine2()
select {}
}
- 这样两个goroutine都先获取
mu1
,再获取mu2
,避免了循环依赖。