Gin 到 2026-03-28 的官方文档已经把这块讲得更明确了:
*gin.Context 不是给你当标准 context.Context 到处传的。
真正该往下游传的是:
ctx := c.Request.Context()#1. 给数据库、HTTP 客户端传标准 context
func (s *TicketService) FindByPlate(ctx context.Context, plate string) ([]Ticket, error) {
// 这里把 ctx 继续传给数据库查询、RPC 或外部 HTTP 请求
return nil, nil
}
func listTickets(c *gin.Context) {
plate := c.Query("plate")
tickets, err := service.FindByPlate(c.Request.Context(), plate)
if err != nil {
c.Error(err)
return
}
c.JSON(http.StatusOK, gin.H{"data": tickets})
}#2. 用中间件统一加超时
func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
defer cancel()
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}#3. 开 goroutine 时别直接拿原始 c
这是 Gin 官方文档点名强调的坑。
错误心智:
go func() {
log.Println(c.Request.URL.Path)
}()正确做法:
cCopy := c.Copy()
go func() {
log.Println(cCopy.Request.URL.Path)
}()因为 Gin 为了性能会复用 gin.Context。
你跨 goroutine 乱用原始 c,很容易出数据竞争和诡异行为。
#4. 一个更底层的判断标准
需要跨包、跨 I/O、跨超时边界传播
-> 用 c.Request.Context()
需要在 Gin 中间件链里临时共享数据
-> 用 c.Set / c.Get
需要在 goroutine 里读 Gin 请求信息
-> 用 c.Copy()