面试题答案
一键面试使用context控制goroutine生命周期
- 创建context:
- 对于有截止时间需求的任务,使用
context.WithDeadline
。例如:
parent := context.Background() deadline := time.Now().Add(5 * time.Second) ctx, cancel := context.WithDeadline(parent, deadline) defer cancel()
- 对于有超时需求的任务,使用
context.WithTimeout
,它本质上也是基于WithDeadline
实现的:
parent := context.Background() ctx, cancel := context.WithTimeout(parent, 3 * time.Second) defer cancel()
- 对于一般的控制场景,如外部信号触发取消,使用
context.WithCancel
:
parent := context.Background() ctx, cancel := context.WithCancel(parent) defer cancel()
- 对于有截止时间需求的任务,使用
- 传递context:在启动goroutine时,将context作为参数传递进去,例如:
go func(ctx context.Context) { for { select { case <-ctx.Done(): return default: // 执行任务逻辑 } } }(ctx)
- 根据条件决定是否继续执行:在goroutine内部,通过检查
ctx.Err()
来判断是否需要停止。例如:go func(ctx context.Context) { for { if err := ctx.Err(); err != nil { // 根据不同的错误类型(如DeadlineExceeded等)做不同处理 return } // 执行任务逻辑 } }(ctx)
- 处理外部信号:可以使用
os/signal
包来捕获系统关闭信号,然后调用cancel
函数来取消context。例如:sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) go func() { sig := <-sigs fmt.Println() fmt.Println(sig) cancel() }()
context使用边界
- 不要传递nil context:在启动新的goroutine或传递给需要context的函数时,永远不要传递
nil
context,否则可能导致空指针异常。 - 合适的层级传递:context应该在需要的层级传递,不要过度传递到不需要的地方。例如,一些纯粹的内部计算函数如果不依赖于外部控制,就不需要传递context。
- 正确处理取消:一旦context被取消,相关的goroutine应该尽快退出并清理资源,避免资源泄漏。
性能优化避免资源浪费和性能损耗
- 避免不必要的context创建:不要在循环内部频繁创建新的context,尽量复用已有的context。例如,如果一个函数被多次调用且每次调用的截止时间或超时时间相同,可以在函数外部创建context并传递进去。
- 优化取消逻辑:在goroutine内部检查
ctx.Done()
或ctx.Err()
的频率要适当。过于频繁检查会增加额外开销,而检查太少可能导致任务不能及时响应取消信号。一般来说,在每个关键操作或一定时间间隔内检查比较合适。 - 资源及时清理:当context取消时,及时清理相关的资源,如关闭文件、数据库连接等。避免因为未及时清理资源而导致后续资源耗尽的问题。