
在 go web 开发中,http 请求默认由独立 goroutine 处理,因此通常无需为整个请求逻辑再启新 goroutine;但当需并行执行多个 i/o 操作(如同时查库与调用 api),或需在响应后异步执行后台任务(如日志、通知)时,则应有节制地启用额外 goroutine。
在 go web 开发中,http 请求默认由独立 goroutine 处理,因此通常无需为整个请求逻辑再启新 goroutine;但当需并行执行多个 i/o 操作(如同时查库与调用 api),或需在响应后异步执行后台任务(如日志、通知)时,则应有节制地启用额外 goroutine。
Go 的 net/http 包天然为每个 HTTP 请求分配一个独立 goroutine,这意味着你已处于并发上下文中——不必、也不应将整个请求处理逻辑再次包裹进 go 语句中。这种嵌套启动不仅无性能增益,反而会引入竞态风险、上下文丢失(如 r.Context() 超时失效)、资源泄漏(如未关闭 response body)等隐患:
// ❌ 错误示例:无意义的 goroutine 嵌套
func badHandler(w http.ResponseWriter, r *http.Request) {
go func() {
// r.Context() 可能已取消,w 不可再写入
db.QueryRowContext(r.Context(), ...)
w.Write([]byte("done")) // panic: write on closed connection
}()
}
真正需要显式使用 goroutine 的场景有两个典型模式:
✅ 场景一:并行 I/O,缩短总延迟
当多个后端依赖(如数据库查询、外部 API 调用、缓存读取)彼此独立、无数据依赖时,应并行发起以减少串行等待时间。务必配合 sync.WaitGroup 或 errgroup.Group 等同步机制确保结果收集与错误处理:
func parallelHandler(w http.ResponseWriter, r *http.Request) {
var (
dbResult string
apiResult string
err error
)
eg, ctx := errgroup.WithContext(r.Context())
eg.Go(func() error {
res, e := queryDB(ctx)
if e == nil {
dbResult = res
}
return e
})
eg.Go(func() error {
res, e := callRemoteAPI(ctx)
if e == nil {
apiResult = res
}
return e
})
if err = eg.Wait(); err != nil {
http.Error(w, "Service unavailable", http.StatusServiceUnavailable)
return
}
// 组合结果并响应
json.NewEncoder(w).Encode(map[string]string{
"db": "ok", "api": "ok",
"data": dbResult + apiResult,
})
}
⚠️ 注意:所有子 goroutine 必须接收并传递
r.Context(),以便在客户端断连或超时时及时取消操作,避免 goroutine 泄漏。
✅ 场景二:响应后异步执行(Fire-and-forget)
当某些操作不影响用户响应(如审计日志、指标上报、邮件发送),应在 w.Write 或 w.WriteHeader 之后启动 goroutine,并确保不引用 w 或已失效的 r:
func fireAndForgetHandler(w http.ResponseWriter, r *http.Request) {
// 正常处理并立即响应
data := processRequest(r)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
// 启动后台任务(注意:不传 w/r,仅传必要参数)
go func(userID string, reqID string) {
// 使用独立 context(如 background 或带短 timeout 的 context)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
logRequest(ctx, userID, reqID)
sendAnalytics(ctx, userID, reqID)
// 即使失败也不影响主流程
}(getUserID(r), getReqID(r))
}
? 总结原则
- ✅ 默认复用 HTTP handler 的 goroutine,按顺序执行依赖链(DB → API → 渲染);
- ✅ 并行:仅对相互独立、耗时长、I/O 密集的操作启用 goroutine,并统一管理生命周期;
- ✅ 异步:响应完成后启动 goroutine,绝不阻塞主流程,且避免持有
http.ResponseWriter或已过期的*http.Request; - ❌ 禁止无同步、无上下文、无错误处理的裸
go func(){}; - ? 推荐工具:优先使用
errgroup.Group替代原始sync.WaitGroup,它天然支持context取消和错误传播。
合理运用 goroutine,不是越多越好,而是让并发服务于确定性、可观测性与资源效率。










