面试题答案
一键面试- 原理:
- 在 Go 语言中,Goroutine 阻塞通常发生在等待资源(如网络 I/O、文件 I/O、channel 操作等)的时候。当一个 Goroutine 执行这些操作时,如果资源不可用,它会进入阻塞状态,让出 CPU 时间片给其他可运行的 Goroutine。
- 使用
pprof
工具:- 引入
runtime/pprof
包: 在代码中引入runtime/pprof
包,例如:package main import ( "fmt" "net/http" _ "net/http/pprof" "time" )
- 启动
pprof
HTTP 服务: 在main
函数中启动一个 HTTP 服务来暴露pprof
数据,如:func main() { go func() { http.ListenAndServe("localhost:6060", nil) }() // 你的包含多个 Goroutine 的业务逻辑代码 time.Sleep(10 * time.Second) }
- 分析
pprof
数据:- CPU 分析:通过访问
http://localhost:6060/debug/pprof/profile
,可以获取 CPU 性能分析数据。如果某个 Goroutine 长时间占用 CPU,它可能并没有阻塞。 - Goroutine 分析:访问
http://localhost:6060/debug/pprof/goroutine?debug=2
,这个页面会展示所有活跃的 Goroutine 的堆栈信息。通过分析堆栈信息,可以判断 Goroutine 是否处于阻塞状态。例如,如果一个 Goroutine 停留在网络 I/O 相关的函数(如net/http
包中的Do
方法),或者文件 I/O 相关函数(如os.Read
),那么它很可能处于阻塞状态。
- CPU 分析:通过访问
- 引入
- 使用
runtime/debug.Stack
:- 在关键位置获取堆栈:
在代码中,你可以在一些关键位置,比如在可能出现阻塞的操作前后,使用
runtime/debug.Stack
函数获取当前 Goroutine 的堆栈信息。例如:package main import ( "fmt" "runtime/debug" ) func blockedGoroutine() { fmt.Println("Goroutine started") stack1 := debug.Stack() // 假设这里有一个可能阻塞的操作,如从 channel 读取数据 var ch chan int <-ch stack2 := debug.Stack() fmt.Printf("Stack before blocking: %s\n", stack1) fmt.Printf("Stack after blocking: %s\n", stack2) }
- 分析堆栈信息: 通过比较操作前后的堆栈信息,如果操作后堆栈没有更新或者停留在某个特定的函数(如等待 channel 数据的函数),可以推断该 Goroutine 处于阻塞状态。
- 在关键位置获取堆栈:
在代码中,你可以在一些关键位置,比如在可能出现阻塞的操作前后,使用