面试题答案
一键面试代码设计阶段预防死锁
- 避免循环依赖:确保资源的获取顺序一致,避免形成循环等待。例如,在多个
Goroutine
中获取锁或通道时,都按照相同的顺序进行操作。
// 错误示例,可能导致死锁
// var mu1 sync.Mutex
// var mu2 sync.Mutex
//
// func goroutine1() {
// mu1.Lock()
// defer mu1.Unlock()
// mu2.Lock()
// defer mu2.Unlock()
// // 业务逻辑
// }
//
// func goroutine2() {
// mu2.Lock()
// defer mu2.Unlock()
// mu1.Lock()
// defer mu1.Unlock()
// // 业务逻辑
// }
// 正确示例,获取锁顺序一致
// func goroutine1() {
// mu1.Lock()
// defer mu1.Unlock()
// mu2.Lock()
// defer mu2.Unlock()
// // 业务逻辑
// }
//
// func goroutine2() {
// mu1.Lock()
// defer mu1.Unlock()
// mu2.Lock()
// defer mu2.Unlock()
// // 业务逻辑
// }
- 合理设计通道使用:
- 避免无缓冲通道双向阻塞:如果使用无缓冲通道进行通信,确保发送和接收操作在不同的
Goroutine
中同时准备好,否则可能导致死锁。例如,在一个Goroutine
中发送数据到无缓冲通道,而另一个Goroutine
需要及时接收。
- 避免无缓冲通道双向阻塞:如果使用无缓冲通道进行通信,确保发送和接收操作在不同的
// 错误示例,可能死锁
// ch := make(chan int)
// go func() {
// ch <- 1
// }()
// // 这里没有其他Goroutine接收数据,会导致发送操作阻塞,形成死锁
- 设置合理的缓冲通道大小:根据实际需求设置缓冲通道的大小,避免因缓冲区满而导致发送操作阻塞,或缓冲区空而导致接收操作阻塞。
// 合理设置缓冲通道大小
ch := make(chan int, 10)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
for val := range ch {
// 处理接收到的值
}
- 使用超时机制:在通道操作上使用
select
结合time.After
设置超时,防止Goroutine
因长时间等待而死锁。
ch := make(chan int)
select {
case val := <-ch:
// 处理接收到的值
case <-time.After(time.Second):
// 超时处理
}
运行时检测死锁
- Go 1.16+的内置支持:Go语言在1.16版本及以后,运行时在检测到死锁时会自动打印详细的死锁信息,包括涉及的
Goroutine
堆栈跟踪。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
var mu1 sync.Mutex
var mu2 sync.Mutex
go func() {
defer wg.Done()
mu1.Lock()
defer mu1.Unlock()
time.Sleep(time.Second)
mu2.Lock()
defer mu2.Unlock()
}()
go func() {
defer wg.Done()
mu2.Lock()
defer mu2.Unlock()
time.Sleep(time.Second)
mu1.Lock()
defer mu1.Unlock()
}()
wg.Wait()
}
当运行这段可能导致死锁的代码时,Go运行时会检测到死锁并输出类似如下信息:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0xc00001a0d8, 0x0, 0x0)
/usr/local/go/src/runtime/sema.go:71 +0x39
sync.(*Mutex).Lock(0xc00001a0d8)
/usr/local/go/src/sync/mutex.go:81 +0x99
main.main.func2()
/home/user/go/src/yourpackage/main.go:22 +0x49
created by main.main
/home/user/go/src/yourpackage/main.go:19 +0x67
goroutine 6 [semacquire]:
sync.runtime_SemacquireMutex(0xc00001a0e8, 0x0, 0x0)
/usr/local/go/src/runtime/sema.go:71 +0x39
sync.(*Mutex).Lock(0xc00001a0e8)
/usr/local/go/src/sync/mutex.go:81 +0x99
main.main.func1()
/home/user/go/src/yourpackage/main.go:15 +0x49
created by main.main
/home/user/go/src/yourpackage/main.go:14 +0x4b
- 使用
go tool trace
:可以使用go tool trace
工具来分析程序的执行轨迹,它能帮助发现死锁等并发问题。在程序中添加如下代码来生成跟踪数据:
package main
import (
"context"
"fmt"
"os"
"runtime/trace"
"sync"
"time"
)
func main() {
f, err := os.Create("trace.out")
if err != nil {
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil {
panic(err)
}
defer trace.Stop()
var wg sync.WaitGroup
wg.Add(2)
var mu1 sync.Mutex
var mu2 sync.Mutex
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go func() {
defer wg.Done()
select {
case <-ctx.Done():
return
default:
mu1.Lock()
defer mu1.Unlock()
time.Sleep(time.Second)
mu2.Lock()
defer mu2.Unlock()
}
}()
go func() {
defer wg.Done()
select {
case <-ctx.Done():
return
default:
mu2.Lock()
defer mu2.Unlock()
time.Sleep(time.Second)
mu1.Lock()
defer mu1.Unlock()
}
}()
wg.Wait()
}
运行程序生成trace.out
文件后,使用go tool trace trace.out
命令,在浏览器中打开生成的报告,通过分析Goroutine
的状态和执行顺序来发现死锁问题。