每日一Go-12、Go语言并发核心Context详解:从超时控制到请求追踪

72 阅读3分钟

1、Context 是什么?

context.Context 是 Go 用来控制协程生命周期、超时、取消信号的机制。 它的主要作用是:在多个 协程之间传递取消信号、超时时间、以及请求相关的数据。就像在复杂的系统中,你需要一个“指挥官”来告诉各个工人何时开始、何时停止工作,Context 就是那个“指挥官”。

2、Context 的常见用途

2.1. 超时控制

当一个请求超过设定时间仍未完成,可以自动取消,防止系统资源被长期占用。

2.2. 取消信号传播

如果上层任务取消,所有关联的子 Goroutine 都能立即收到信号,及时停止。

2.3. 传递请求范围内的元数据

例如:用户ID、Trace ID、Token等,可在整个请求链中共享。

3、Context 的多种创建方式


// 1 通常用于main或测试
ctx := context.Background()
// 2 不确定用哪个时,使用的占位符
ctx := context.TODO()
// 3 创建可取消的子Context
ctx,cancel := context.WithCancel(context.Background())
// 4 超时自动取消
ctx,cancel := context.WithTimeout(context.Background(),5*time.Second)
// 5 到指定时间自动取消
ctx,cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second))
// 6 携带键值数据
ctx := context.WithValue(context.Background(), "userID", 123)

4、实际使用示例

4.1:请求超时控制


package main
import (
    "context"
    "fmt"
    "time"
)
func main() {
    // 超时自动取消
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    ch := make(chan string)
    go func() {
        time.Sleep(5 * time.Second)
        ch <- "任务完成"
    }()
    select {
    case <-ctx.Done():
        fmt.Println("超时:", ctx.Err())
    case result := <-ch:
        fmt.Println(result)
    }
}

输出如图:

图片

4.2:取消信号的传播

package main
import (
    "context"
    "fmt"
    "time"
)
func worker(ctx context.Context, name string) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println(name, "收到取消信号,退出")
            return
        default:
            fmt.Println(name, "正在工作中...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}
func main() {
    // 创建可取消的子Context
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx, "worker-1")
    go worker(ctx, "worker-2")
    go worker(ctx, "worker-3")
    time.Sleep(2 * time.Second)
    fmt.Println("主程序:任务取消")
    // 主动取消,发送取消信号
    cancel()
    time.Sleep(1 * time.Second)
}

输出如图:

图片

4.3 传递请求范围内的元数据

package main
import (
    "context"
    "fmt"
)
// 定义上下文键的类型(避免不同包的 key 冲突)
type ctxKey string
const (
    CtxUserID  ctxKey = "userID"
    CtxTraceID ctxKey = "traceID"
)
// 模拟业务逻辑层
func serviceCall(ctx context.Context) {
    // 从 Context 中获取值
    userID := ctx.Value(CtxUserID)
    traceID := ctx.Value(CtxTraceID)
    fmt.Println("服务处理中...")
    fmt.Printf("TraceID: %v | UserID: %v\n", traceID, userID)
    // 模拟调用数据库操作
    dbQuery(ctx)
}
// 模拟数据库查询
func dbQuery(ctx context.Context) {
    fmt.Println("数据库查询中...")
    fmt.Printf("当前 TraceID: %v\n", ctx.Value(CtxTraceID)) // 从Context中取值
}
func main() {
    // 创建根 Context
    ctx := context.Background()
    // 在 Context 中附加元数据
    ctx = context.WithValue(ctx, CtxUserID, 1)
    ctx = context.WithValue(ctx, CtxTraceID, "TRACE-ID-1")
    // 将带数据的 Context 传递下去
    serviceCall(ctx)
}

输出如图:

图片

5、在网络服务中的重要性

在构建 Web 服务或微服务时,Context 通常用于:

  • 控制数据库查询或 API 请求超时;

  • 请求被用户取消时,自动停止后台任务;

  • 分布式追踪(Tracing)中传递 TraceID;

  • 限制协程泄漏(防止任务永远不退出)。


想象你是一个登山队的队长,带领多个队员(协程)一起登山。 Context 就像你的“对讲机”:

你说“下山!”(cancel())——所有队员都会立刻收到信号停止行动;

你设定“如果两小时内没到顶,就撤!”(WithTimeout)——时间到了自动通知;

你可以在对讲机里传递信息(WithValue)——比如“目标坐标”或“队伍编号”。

没有 Context,队伍可能迷路;有了 Context,整个队伍协同高效,进退有度。

源码地址

1、公众号“Codee君”回复“源码”获取源码

2、pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!