面试题答案
一键面试1. 检测Goroutine卡住的机制设计
可以通过使用context.Context
和定时器来检测Goroutine是否卡住。context.Context
用于控制Goroutine的生命周期,定时器用于设置一个时间限制。
以下是一个简单的示例代码:
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context) {
select {
case <-ctx.Done():
// 如果收到取消信号,正常退出
return
case <-time.After(5 * time.Second):
// 如果5秒内没有收到取消信号,认为Goroutine卡住
fmt.Println("Goroutine may be stuck")
}
// 实际工作逻辑
fmt.Println("Worker is working")
}
2. 解决策略
- 优化代码逻辑:检查Goroutine内部的业务逻辑,查看是否有死循环、长时间阻塞的操作(如未设置超时的网络请求等)。例如,如果Goroutine中进行数据库查询,需要设置合理的查询超时。
- 增加日志输出:在Goroutine内部关键节点增加日志输出,便于定位问题。例如:
func worker(ctx context.Context) {
fmt.Println("Worker started")
select {
case <-ctx.Done():
return
case <-time.After(5 * time.Second):
fmt.Println("Goroutine may be stuck")
}
fmt.Println("Worker is working")
fmt.Println("Worker finished")
}
- 使用监控工具:借助Go的pprof工具,分析程序的CPU和内存使用情况,找出性能瓶颈。可以通过
net/http/pprof
包来暴露相关指标,例如:
package main
import (
"context"
"fmt"
"net/http"
_ "net/http/pprof"
"time"
)
func worker(ctx context.Context) {
select {
case <-ctx.Done():
return
case <-time.After(5 * time.Second):
fmt.Println("Goroutine may be stuck")
}
fmt.Println("Worker is working")
}
func main() {
go func() {
http.ListenAndServe(":6060", nil)
}()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
go worker(ctx)
time.Sleep(15 * time.Second)
}
在浏览器中访问http://localhost:6060/debug/pprof/
,可以查看相关性能分析数据。
完整示例
package main
import (
"context"
"fmt"
"net/http"
_ "net/http/pprof"
"time"
)
func worker(ctx context.Context) {
fmt.Println("Worker started")
select {
case <-ctx.Done():
fmt.Println("Worker stopped")
return
case <-time.After(5 * time.Second):
fmt.Println("Goroutine may be stuck")
}
fmt.Println("Worker is working")
fmt.Println("Worker finished")
}
func main() {
go func() {
http.ListenAndServe(":6060", nil)
}()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
go worker(ctx)
time.Sleep(15 * time.Second)
}
此代码通过设置context
的超时时间和定时器,检测Goroutine是否卡住,并给出了一些解决策略的示例。