面试题答案
一键面试检测死锁情况
在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
避免死锁的方法
- 固定锁的获取顺序:所有goroutine都按照相同的顺序获取锁。例如,无论是A还是B,都先获取m1,再获取m2。这样就不会出现相互等待对方释放锁的情况。
- 使用超时机制:在获取锁时设置一个超时时间。如果在规定时间内没有获取到锁,则放弃获取并进行相应处理,避免无限期等待。
代码实现(固定锁的获取顺序)
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
锁的顺序执行,从而避免了死锁。