分享
  1. 首页
  2. 文章

Context高级用法

SuperStupid1 · · 375 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

# 🚀 `context.Context` 在实际项目中的高级用法 --- ## 一、基本结构回顾 ```go type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} } ``` - `Done()`:返回一个 channel,当上下文被取消或超时时关闭。 - `Err()`:获取取消的原因(如 context.Canceled、context.DeadlineExceeded)。 - `Value()`:传递请求作用域的数据。 - `Deadline()`:获取截止时间。 --- ## 二、实际项目中的高级用法 ### ✅ 1. 控制并发任务的退出(Goroutine Pool) #### 场景: 你有一个后台服务处理多个任务,比如从队列中消费消息。你希望在服务关闭时能优雅地停止所有 goroutine。 #### 示例代码: ```go func worker(ctx context.Context, id int) { for { select { case <-ctx.Done(): fmt.Printf("Worker %d stopped: %v\n", id, ctx.Err()) return default: fmt.Printf("Worker %d is working...\n", id) time.Sleep(500 * time.Millisecond) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) for i := 1; i <= 3; i++ { go worker(ctx, i) } time.Sleep(2 * time.Second) cancel() // 停止所有 worker time.Sleep(1 * time.Second) } ``` > ⚠️ 注意:使用 `select` + `ctx.Done()` 是防止 goroutine 泄漏的关键。 --- ### ✅ 2. 设置请求超时(HTTP / RPC / DB 查询) #### 场景: 你发起一个外部 HTTP 请求或数据库查询,但你不希望它永远等待,而是设置最大等待时间。 #### 示例代码: ```go func fetchWithTimeout() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() req, _ := http.NewRequest("GET", "https://slow-api.com/data", nil) req = req.WithContext(ctx) resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Println("Request failed:", err) return } fmt.Println("Response status:", resp.Status) } ``` > 如果超过 2 秒还没返回结果,请求将自动取消。 --- ### ✅ 3. 链路追踪(Trace ID / Request ID) #### 场景: 你在构建一个微服务系统,每个请求都要带上一个唯一标识(trace_id),方便日志追踪和调试。 #### 实现方式: 使用 `context.WithValue()` 来携带 trace_id。 ```go func handleRequest(traceID string) { ctx := context.WithValue(context.Background(), "trace_id", traceID) go processTask(ctx) time.Sleep(1 * time.Second) } func processTask(ctx context.Context) { traceID, _ := ctx.Value("trace_id").(string) fmt.Printf("[%s] Processing task...\n", traceID) } ``` > ⚠️ 注意: > > - `WithValue` 只适合传**不可变的请求元信息**,不能传复杂对象。 > - 推荐使用类型安全的 key(避免冲突): ```go type contextKey string const TraceIDKey contextKey = "trace_id" ``` --- ### ✅ 4. 多级 Cancel 传播(嵌套 Context) #### 场景: 你有多个 goroutine 并行执行任务,它们共享一个父 context。一旦父 context 被取消,所有子任务也应该终止。 #### 示例: ```go func parentChildContext() { parentCtx, parentCancel := context.WithCancel(context.Background()) childCtx, childCancel := context.WithCancel(parentCtx) defer childCancel() go func() { for { select { case <-childCtx.Done(): fmt.Println("Child stopped:", childCtx.Err()) return default: fmt.Println("Child is running...") time.Sleep(500 * time.Millisecond) } } }() time.Sleep(2 * time.Second) parentCancel() // 取消父 context,子 context 也会自动取消 time.Sleep(1 * time.Second) } ``` 输出: ``` Child is running... Child is running... Child stopped: context canceled ``` --- ### ✅ 5. 结合中间件统一管理请求上下文(Go Web 框架) #### 场景: 在 Gin、Echo 等 Web 框架中,为每个请求创建一个带超时的 context,并注入 trace_id、user_id 等信息。 #### Gin 示例: ```go 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() } } ``` 注册中间件: ```go r := gin.Default() r.Use(TimeoutMiddleware(5 * time.Second)) ``` > 所有后续 handler 中使用的 `c.Request.Context()` 都会受到超时控制。 --- ### ✅ 6. 结合数据库操作(ORM / SQL) 很多 ORM 库(如 GORM)支持 context,你可以为数据库操作设置超时: ```go ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var user User db.WithContext(ctx).Where("id = ?", 1).First(&user) ``` 如果查询超过 3 秒,会自动中断并返回错误。 --- ### ✅ 7. 资源清理与释放(如锁、连接池) #### 场景: 你在获取分布式锁、连接池资源后,需要确保即使发生 panic 或超时也能正确释放资源。 ```go func withLockAndCleanup(ctx context.Context) { lock := NewRedisLock(rdb, "my_lock", "worker_1", 10*time.Second) if !lock.Acquire(ctx) { fmt.Println("Failed to acquire lock") return } defer lock.Release(ctx) // do something fmt.Println("Processing with lock") } ``` > 即使 panic,`defer` 也能保证锁被释放。 --- ## 🧩 总结:Context 的常见用途分类 | 使用场景 | 描述 | | ---------------------- | ---------------------------------------- | | Goroutine 生命周期控制 | 控制协程何时退出 | | 请求超时控制 | 限制单个请求的最大执行时间 | | 上下文数据传递 | 传递 trace_id、user_id 等信息 | | 取消通知广播 | 父 context 取消,所有子 context 自动取消 | | 链式调用控制 | 微服务间调用链的上下文透传 | | 资源清理 | 锁、连接、文件句柄的释放 | --- ## ✅ 最佳实践建议 | 建议 | 说明 | | ----------------------------------------------------- | --------------------------- | | ❌ 不要滥用 `context.TODO()` 和 `context.Background()` | 应该优先使用传入的 context | | ✅ 使用 `select` 监听 `ctx.Done()` | 防止 goroutine 泄漏 | | ✅ 使用 `defer cancel()` 释放资源 | 防止 context 泄漏 | | ✅ 使用类型安全的 key 传递数据 | 避免 key 冲突 | | ✅ 在中间件中封装 context 控制逻辑 | 统一处理超时、cancel、trace | | ✅ 结合日志记录 trace_id | 方便排查问题 | ---

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
375 次点击 ∙ 1 赞
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏