在处理请求时,有时需要启动 goroutine 进行异步处理。Context 不是并发安全的,需要特殊处理。
直接在 goroutine 中使用 Context 会有问题:
r.GET("/unsafe", func(c *gin.Context) {
go func() {
c.JSON(200, gin.H{"message": "这样做不安全"})
}()
})
这样做可能导致竞态条件,因为多个 goroutine 同时访问 Context。
Gin 提供了 Copy 方法来创建 Context 的副本:
package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/async", func(c *gin.Context) {
cCp := c.Copy()
go func() {
time.Sleep(time.Second)
println("异步处理完成: " + cCp.Request.URL.Path)
}()
c.String(http.StatusOK, "请求已接收,正在后台处理")
})
r.Run(":8080")
}
func AsyncLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
cCp := c.Copy()
go func() {
log.Printf("[%s] %s %s - %d - %v",
time.Now().Format("2006-01-02 15:04:05"),
cCp.Request.Method,
cCp.Request.URL.Path,
cCp.Writer.Status(),
time.Since(start),
)
}()
}
}
r.Use(AsyncLogger())
func NotifyOnComplete(c *gin.Context, message string) {
cCp := c.Copy()
go func() {
requestID, _ := cCp.Get("requestID")
user, _ := cCp.Get("currentUser")
sendNotification(requestID.(string), user.(*User).Email, message)
}()
}
r.POST("/order", Auth(), func(c *gin.Context) {
var order Order
c.ShouldBindJSON(&order)
createOrder(order)
NotifyOnComplete(c, "订单创建成功")
c.JSON(200, gin.H{"message": "订单已创建"})
})
func AsyncSaveAnalytics(c *gin.Context, data map[string]interface{}) {
cCp := c.Copy()
go func() {
data["requestID"], _ = cCp.Get("requestID")
data["clientIP"] = cCp.ClientIP()
data["userAgent"] = cCp.UserAgent()
data["timestamp"] = time.Now()
saveToDatabase(data)
}()
}
r.GET("/track", func(c *gin.Context) {
AsyncSaveAnalytics(c, map[string]interface{}{
"action": "page_view",
"page": c.Query("page"),
})
c.String(200, "OK")
})
r.GET("/process", func(c *gin.Context) {
resultChan := make(chan string)
cCp := c.Copy()
go func() {
time.Sleep(time.Second)
resultChan <- "处理完成: " + cCp.Request.URL.Path
}()
select {
case result := <-resultChan:
c.String(200, result)
case <-time.After(2 * time.Second):
c.String(408, "处理超时")
}
})
func fetchUserData(userID int) interface{} {
time.Sleep(100 * time.Millisecond)
return map[string]interface{}{"id": userID, "name": "用户" + string(rune(userID))}
}
func fetchUserOrders(userID int) interface{} {
time.Sleep(150 * time.Millisecond)
return []string{"订单1", "订单2"}
}
r.GET("/user/:id/detail", func(c *gin.Context) {
userID := c.Param("id")
var wg sync.WaitGroup
userData := make(chan interface{}, 1)
orderData := make(chan interface{}, 1)
cCp := c.Copy()
wg.Add(2)
go func() {
defer wg.Done()
userData <- fetchUserData(userID)
}()
go func() {
defer wg.Done()
orderData <- fetchUserOrders(userID)
}()
go func() {
wg.Wait()
close(userData)
close(orderData)
}()
c.JSON(200, gin.H{
"user": <-userData,
"orders": <-orderData,
"path": cCp.Request.URL.Path,
})
})
Go 标准库的 context.Context 用于控制 goroutine 生命周期:
r.GET("/with-timeout", func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
cCp := c.Copy()
resultChan := make(chan string, 1)
go func() {
time.Sleep(time.Second)
resultChan <- "处理完成: " + cCp.Request.URL.Path
}()
select {
case result := <-resultChan:
c.String(200, result)
case <-ctx.Done():
c.String(408, "请求超时")
}
})
c.Copy() 的返回值context.Context 控制超时Context 本身不是并发安全的,在 goroutine 中使用需要调用 Copy() 创建副本。异步处理时注意资源管理和错误处理,避免 goroutine 泄漏。结合标准库的 context.Context 可以更好地控制 goroutine 生命周期。