MST

星途 面试题库

面试题:Go语言中Mutex锁死锁场景分析与解决

假设有两个goroutine,分别为A和B。A先获取Mutex锁m1,然后尝试获取m2;B先获取Mutex锁m2,然后尝试获取m1,这样就可能导致死锁。请描述如何检测这种死锁情况,并提出至少两种避免这种死锁的方法,同时编写代码实现其中一种方法。
35.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

检测死锁情况

在Go语言中,运行时系统本身会检测死锁。当程序发生死锁时,Go运行时会输出死锁相关信息到标准错误输出,包括涉及的goroutine以及它们当前正在阻塞的操作。例如,在程序运行时,如果出现死锁,会类似如下输出:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x1400009c020)
        /usr/local/go/src/runtime/sema.go:56 +0x38
sync.(*Mutex).lock(0x1400009c020)
        /usr/local/go/src/sync/mutex.go:81 +0x8c
sync.(*Mutex).Lock(...)
        /usr/local/go/src/sync/mutex.go:49
main.main.func1()
        /Users/user/go/src/github.com/user/test/main.go:11 +0x54

goroutine 2 [semacquire]:
sync.runtime_Semacquire(0x1400009c040)
        /usr/local/go/src/runtime/sema.go:56 +0x38
sync.(*Mutex).lock(0x1400009c040)
        /usr/local/go/src/sync/mutex.go:81 +0x8c
sync.(*Mutex).Lock(...)
        /usr/local/go/src/sync/mutex.go:49
main.main.func2()
        /Users/user/go/src/github.com/user/test/main.go:16 +0x54

goroutine 3 [syscall]:
os/signal.signal_recv(0x1400000c008)
        /usr/local/go/src/runtime/sigqueue.go:137 +0x90
os/signal.loop()
        /usr/local/go/src/os/signal/signal_unix.go:23 +0x20
created by os/signal.init.0
        /usr/local/go/src/os/signal/signal_unix.go:28 +0x40

避免死锁的方法

  1. 固定锁的获取顺序:所有goroutine都按照相同的顺序获取锁。例如,无论是A还是B,都先获取m1,再获取m2。这样就不会出现相互等待对方释放锁的情况。
  2. 使用超时机制:在获取锁时设置一个超时时间。如果在规定时间内没有获取到锁,则放弃获取并进行相应处理,避免无限期等待。

代码实现(固定锁的获取顺序)

package main

import (
    "fmt"
    "sync"
)

var (
    m1 sync.Mutex
    m2 sync.Mutex
)

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

    // goroutine A
    go func() {
        defer wg.Done()
        m1.Lock()
        defer m1.Unlock()
        m2.Lock()
        defer m2.Unlock()
        fmt.Println("goroutine A got both locks")
    }()

    // goroutine B
    go func() {
        defer wg.Done()
        m1.Lock()
        defer m1.Unlock()
        m2.Lock()
        defer m2.Unlock()
        fmt.Println("goroutine B got both locks")
    }()

    wg.Wait()
}

在上述代码中,两个goroutine都按照先获取m1锁,再获取m2锁的顺序执行,从而避免了死锁。