golang的Gin框架Pool缓存池异步Context异常BUG

1 篇文章 0 订阅
1 篇文章 0 订阅

引言

前两天做的数据导入功能,考虑到后端处理比较慢,所以前端上传完文件,后端开启协程异步进行处理,同时立即返回给前端一个上传成功标识及本次上传的uuid。前端拿着返回的uuid进行轮训查询后端处理状态。逻辑上没有问题,但偶现获取 ctx 中存储的信息为空。

简化后代码如下:

package main

import (
 "fmt"
 "github.com/gin-gonic/gin"
 "time"
)

func main() {
 r := gin.Default()

 // 上传接口
 r.GET("/upload", func(c *gin.Context) {
  // 设置tid的值
  c.Set("tid", "abc")
  // 异步处理业务
  go func() {
   // 打印 ctx 的地址,以及tid的值
   fmt.Printf("\nupload-1------:%p, tid:%+v\n", c, c.Value("tid"))
   // 处理业务,使用sleep替代处理逻辑
   time.Sleep(time.Second * 10)
   // 再次打印 ctx 的地址,以及tid的值
   fmt.Printf("\nupload-2======:%p, tid:%+v\n", c, c.Value("tid"))
  }()
  // 立即返回给前端
  c.JSON(200, gin.H{
   "message": "upload pong",
   "uuid":    "123",
  })
 })

 // 获取上传后的处理状态
 r.GET("/check-status", func(c *gin.Context) {
  fmt.Printf("\ncheck-status ctx***********:%p\n", c)
  c.JSON(200, gin.H{
   "message": "check status pong",
   "uuid":    c.Query("uuid"),
  })
 })

 // 启动http服务
 if err := r.Run(":80"); err != nil {
  fmt.Println("listen err")
 }
}

经过一顿分析,发现问题是Gin的缓冲池,引起的。想要查看详细分析过程请搜索微信公众号:大胡几哥哥,或访问:golang的Gin框架异步Context异常

Gin使用缓冲池是为了提高性能,但我没有正确使用导致多个http请求,使用相同的ctx,而新请求会把缓冲池中的ctx存储的内容清空,所以异步中未完成的业务获取ctx中的内容就失败。解决办法就是使用 `c.copy` 复制一份ctx:

package main

import (
 "fmt"
 "github.com/gin-gonic/gin"
 "time"
)

func main() {
 r := gin.Default()

 // 上传接口
 r.GET("/upload", func(c *gin.Context) {
  // 设置tid的值
  c.Set("tid", "abc")
  ctx := c.Copy()
  // 异步处理业务
  go func() {
   // 打印 ctx 的地址,以及tid的值
   fmt.Printf("\nupload-1------:%p, tid:%+v\n", ctx, ctx.Value("tid"))
   // 处理业务,使用sleep替代处理逻辑
   time.Sleep(time.Second * 10)
   // 再次打印 ctx 的地址,以及tid的值
   fmt.Printf("\nupload-2======:%p, tid:%+v\n", ctx, ctx.Value("tid"))
  }()
  // 立即返回给前端
  c.JSON(200, gin.H{
   "message": "upload pong",
   "uuid":    "123",
  })
 })

 // 获取上传后的处理状态
 r.GET("/check-status", func(c *gin.Context) {
  fmt.Printf("\ncheck-status ctx***********:%p\n", c)
  c.JSON(200, gin.H{
   "message": "check status pong",
   "uuid":    c.Query("uuid"),
  })
 })

 // 启动http服务
 if err := r.Run(":80"); err != nil {
  fmt.Println("listen err")
 }
}

点击查看更多文章

更新精彩文章请关注微信公众号:大胡几哥哥

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我可以为您提供一些关于使用 Golang Gin 框架进行项目实战的建议。 首先,您需要安装 GolangGin 框架。安装过程可以参考官方文档进行操作。然后,您可以按照以下步骤进行项目实战: 1. 创建项目结构 在您的 GOPATH 目录下创建一个新的项目文件夹。通常情况下,您需要为项目创建以下子文件夹: ``` . ├── main.go ├── config │ ├── config.go │ └── ... ├── controller │ ├── home_controller.go │ └── ... ├── middleware │ ├── auth.go │ └── ... ├── model │ ├── user.go │ └── ... ├── router │ ├── router.go │ └── ... └── service ├── user_service.go └── ... ``` 2. 创建路由 在 router 文件夹创建一个 router.go 文件,并编写以下代码: ```go package router import ( "github.com/gin-gonic/gin" "your-project/controller" "your-project/middleware" ) func SetupRouter() *gin.Engine { r := gin.Default() // 间件 r.Use(middleware.AuthMiddleware()) // 路由 r.GET("/", controller.HomePageHandler) return r } ``` 在上面的代码,我们使用gin 框架gin.Default() 函数来创建一个默认的 gin.Engine 实例,并使用间件来进行身份验证。然后我们定义了一个路由,将根路径映射到 HomePageHandler 方法。 3. 创建控制器 在 controller 文件夹创建 home_controller.go 文件,并编写以下代码: ```go package controller import ( "github.com/gin-gonic/gin" "your-project/service" ) func HomePageHandler(c *gin.Context) { users := service.GetAllUsers() c.JSON(200, users) } ``` 在上面的代码,我们定义了一个 HomePageHandler 方法,它接收一个 gin.Context 对象作为参数,并使用 service 层的 GetAllUsers 函数获所有用户的数据。然后我们使用 JSON 方法将数据以 JSON 格式返回给客户端。 4. 创建服务层 在 service 文件夹创建 user_service.go 文件,并编写以下代码: ```go package service import "your-project/model" func GetAllUsers() []*model.User { // TODO: 查询数据库获所有用户数据 return []*model.User{} } ``` 在上面的代码,我们定义了一个 GetAllUsers 函数,它返回一个包含所有用户数据的切片。在实际项目,我们需要查询数据库来获这些数据。 5. 创建模型 在 model 文件夹创建 user.go 文件,并编写以下代码: ```go package model type User struct { ID int `json:"id"` Name string `json:"name"` Age int `json:"age"` } ``` 在上面的代码,我们定义了一个 User 结构体,它包含三个字段:ID、Name 和 Age。这些字段将用于存储每个用户的信息。 6. 创建配置文件 在 config 文件夹创建 config.go 文件,并编写以下代码: ```go package config const ( DB_HOST = "localhost" DB_PORT = "3306" DB_USERNAME = "root" DB_PASSWORD = "password" DB_NAME = "your-database-name" ) ``` 在上面的代码,我们定义了一些常量,用于存储数据库配置信息。这些常量将在连接数据库时使用。 7. 连接数据库 在 service 层的 GetAllUsers 函数,我们需要连接数据库来获所有用户数据。可以使用以下代码来连接 MySQL 数据库: ```go package service import ( "database/sql" "fmt" "your-project/config" "your-project/model" ) func GetAllUsers() []*model.User { // 连接数据库 db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", config.DB_USERNAME, config.DB_PASSWORD, config.DB_HOST, config.DB_PORT, config.DB_NAME, )) if err != nil { panic(err) } defer db.Close() // 查询所有用户数据 rows, err := db.Query("SELECT * FROM users") if err != nil { panic(err) } // 解析查询结果 users := []*model.User{} for rows.Next() { user := &model.User{} err := rows.Scan(&user.ID, &user.Name, &user.Age) if err != nil { panic(err) } users = append(users, user) } return users } ``` 在上面的代码,我们使用 sql.Open 函数来打开 MySQL 数据库连接。然后我们使用 db.Query 函数来查询所有用户数据,并使用 rows.Scan 函数将查询结果解析为 User 结构体的切片。 以上就是使用 Golang Gin 框架进行项目实战的流程。当然,在实际项目,您需要根据自己的需求进行相应的修改和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值