Go技巧:用Context包优雅管理goroutine生命周期
在Go并发编程中,goroutine的启动很简单,但如何优雅地停止它们却常被忽视。今天分享一个使用context.Context管理goroutine生命周期的实用技巧。
问题场景
假设我们有一个执行任务的goroutine,需要在主程序退出前或用户取消操作时立即停止:
func worker() {
for {
// 模拟耗时操作
time.Sleep(time.Second)
fmt.Println("working...")
}
}
这种写法无法从外部控制goroutine的退出,会造成goroutine泄漏。
解决方案:Context + select
利用context.Context和select语句,可以优雅地实现超时控制和主动取消:
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("worker stopped:", ctx.Err())
return
default:
// 执行实际工作
time.Sleep(time.Second)
fmt.Println("working...")
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() // 确保资源释放
go worker(ctx)
// 模拟用户按任意键退出
fmt.Scanln()
cancel() // 手动取消
}
关键点解析
ctx.Done():返回一个只读channel,当context被取消或超时时会关闭select语句:同时监听工作逻辑和取消信号,谁先到达就执行谁defer cancel():即使函数正常返回,也要确保调用cancel释放资源
进阶用法:传递元数据
Context还可以传递请求相关的元数据:
ctx := context.WithValue(context.Background(), "requestID", "req-123")
但在生产代码中,建议将必要参数显式传递,而非依赖context.Value。
总结
使用context管理goroutine生命周期是Go的最佳实践之一。它让你的并发代码更加健壮、可控,避免了资源泄漏和难以调试的并发问题。
记住:创建goroutine时,就要想清楚如何停止它。