MST

星途 面试题库

面试题:Go中Mutex锁死锁场景及避免策略

在Go语言中,描述一个可能导致Mutex锁死锁的常见场景,并阐述如何避免这种死锁。例如,从代码逻辑层面分析,在多个goroutine竞争锁资源时,可能出现哪些情况导致死锁,以及相应的解决办法。
47.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

可能导致Mutex死锁的常见场景

  1. 重复锁定
    • 同一个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.")
}
  1. 未解锁
    • 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.")
}
  1. 循环依赖
    • 多个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,形成了循环依赖,导致死锁。

避免死锁的方法

  1. 确保解锁
    • 使用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.")
}
  1. 避免重复锁定
    • 仔细设计代码逻辑,确保同一个goroutine不会对已经锁定的Mutex再次进行锁定。
  2. 避免循环依赖
    • 对锁的获取顺序进行统一规划,避免形成循环依赖。例如,在多个锁的情况下,所有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,避免了循环依赖。