MST
星途 面试题库

面试题:Go中避免互斥锁死锁的策略

在一个复杂的Go并发程序中,存在多个互斥锁,可能会出现死锁情况。请阐述避免死锁的几种策略,并举例说明如何在代码中实现这些策略。
49.2万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

避免死锁的策略及代码示例

  1. 按顺序加锁
    • 策略阐述:在多个互斥锁存在的情况下,约定所有的goroutine都按照相同的顺序获取锁。这样可以避免循环依赖导致的死锁。
    • 代码示例
package main

import (
    "fmt"
    "sync"
)

var (
    mu1 sync.Mutex
    mu2 sync.Mutex
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    // goroutine 1
    go func() {
        defer wg.Done()
        mu1.Lock()
        defer mu1.Unlock()
        fmt.Println("goroutine 1: acquired mu1")

        mu2.Lock()
        defer mu2.Unlock()
        fmt.Println("goroutine 1: acquired mu2")
    }()

    // goroutine 2
    go func() {
        defer wg.Done()
        mu1.Lock()
        defer mu1.Unlock()
        fmt.Println("goroutine 2: acquired mu1")

        mu2.Lock()
        defer mu2.Unlock()
        fmt.Println("goroutine 2: acquired mu2")
    }()

    wg.Wait()
}
  1. 使用context控制超时
    • 策略阐述:通过设置获取锁的超时时间,如果在规定时间内没有获取到锁,则放弃获取,避免无限等待。
    • 代码示例
package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

var mu sync.Mutex

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    var wg sync.WaitGroup
    wg.Add(1)

    go func() {
        defer wg.Done()
        select {
        case <-ctx.Done():
            fmt.Println("timed out waiting for lock")
        default:
            mu.Lock()
            defer mu.Unlock()
            fmt.Println("acquired lock")
        }
    }()

    wg.Wait()
}
  1. 避免嵌套锁
    • 策略阐述:尽量减少锁的嵌套使用,如果必须嵌套,要仔细规划锁的获取和释放顺序。
    • 代码示例:假设我们有一个简单的场景,原本可能会出现嵌套锁的错误使用。
package main

import (
    "fmt"
    "sync"
)

var (
    muOuter sync.Mutex
    muInner sync.Mutex
)

// 错误的嵌套锁使用方式(可能导致死锁)
// func wrongNestedLock() {
//     muOuter.Lock()
//     defer muOuter.Unlock()

//     muInner.Lock()
//     defer muInner.Unlock()

//     fmt.Println("Inside wrong nested lock")
// }

// 正确的方式,避免嵌套锁
func correctWay() {
    // 先获取外层锁
    muOuter.Lock()
    // 处理外层锁相关逻辑
    outerData := "outer data"
    muOuter.Unlock()

    // 再获取内层锁
    muInner.Lock()
    // 处理内层锁相关逻辑,使用外层数据
    fmt.Println("Using outer data:", outerData)
    muInner.Unlock()
}
  1. 检测死锁
    • 策略阐述:使用Go语言内置的runtime包中的死锁检测机制,在程序运行时检测是否发生死锁,并输出相关信息。
    • 代码示例
package main

import (
    "fmt"
    "runtime"
    "sync"
)

var (
    mu1 sync.Mutex
    mu2 sync.Mutex
)

func main() {
    runtime.SetDeadlockProfileRate(1)

    var wg sync.WaitGroup
    wg.Add(2)

    // goroutine 1
    go func() {
        defer wg.Done()
        mu1.Lock()
        defer mu1.Unlock()
        fmt.Println("goroutine 1: acquired mu1")

        time.Sleep(1 * time.Second)

        mu2.Lock()
        defer mu2.Unlock()
        fmt.Println("goroutine 1: acquired mu2")
    }()

    // goroutine 2
    go func() {
        defer wg.Done()
        mu2.Lock()
        defer mu2.Unlock()
        fmt.Println("goroutine 2: acquired mu2")

        time.Sleep(1 * time.Second)

        mu1.Lock()
        defer mu1.Unlock()
        fmt.Println("goroutine 2: acquired mu1")
    }()

    wg.Wait()
}

当运行这段代码时,如果发生死锁,Go运行时会输出死锁相关的堆栈信息,帮助开发者定位问题。