Go 语言 Context 详解:生命周期管理与变量传递 | Go学习笔记

57 阅读2分钟

Context是什么

首先Context是一个request的上下文,用于做上下文生命周期管理的。可控制gorutine的超时、取消,做信号传递;也可在生命周期内,做全局的变量传递功能(比如请求的用户信息、追踪信息)。

Context接口的核心方法

  1. Deadline() (deadline time.Time, ok bool) 返回截止时间,是否存在截止时间
  2. Done() <-chan struct 用于传递上下文取消信号的只读channel
  3. Err() error 上下文的报错信息
  4. Value(key any) any 提取上下文保存的全局变量(可以用context.WithValue塞入值,用Value提取)

Ctx怎么创建的?

context.Background()

创建一个没有取消、超时的空Context,一般作为根ctx使用

    nctx := ctx.Background()

context.TODO()

创建一个类似ctx.Background()的上下文,但一般作为ctx的临时变量来使用,比如:

    // 假设这里有一个函数,暂时不确定外部会传入什么样的Context
    funcDoSomething := func(ctx context.Context) {
        // 这里先使用TODO作为占位符
        var todoCtx context.Context = context.TODO()
        // 假设根据某个条件来决定是否设置超时
        if someCondition() {
            // 如果条件满足,设置2秒超时
            todoCtx, _ = context.WithTimeout(ctx, 2*time.Second)
        } else {
            // 否则使用传入的Context
            todoCtx = ctx
        }
        // 使用处理后的Context执行其他操作
        //...
    }

context.WithValue(parent Context, key, val any)

往ctx里塞入参数,返回一个包含参数的孩子ctx

// 塞入参数
queryParamKey := "query_param_key"
param := &Param{}
ctx = context.WithValue(ctx, setValueKey, param)

// 提取参数
val := ctx.Value(queryParamKey).(*Param)

context.WithCancel()

context.WithCancel(ctx context) (ctx Context, cancel CancelFunc) 输入父母ctx,返回孩子ctx, 当调用cancel func时,会取消孩子ctx和派生的所有ctx,并向孩子ctx.Done()发送取消信号

ctx, cancelFunc := context.WithCancel(context.Background())
cancelFunc() //调用取消

context.WithCancelCause()

ctx, cancelFunc := context.WithCancelCause(context.Background())
err := errors.New("new error")
cancelFunc(err)

context.Cause() error

返回取消ctx的原因,返回一个error

err := context.Cause()

context.WithDeadline() 和 context.WithTimeout()

都是接收一个根ctx和时间,用于做ctx的时间控制,只是时间的控制方式不一样

第一个WithDeadline,指定时间点,在指定时间超时

第二个WithTimeout,指定时间段,在过了时间段后超时

使用场景

传递全局变量

示例如上文,已贴过

// 塞入参数
queryParamKey := "query_param_key"
param := &Param{}
ctx = context.WithValue(ctx, setValueKey, param)

// 提取参数
val := ctx.Value(queryParamKey).(*Param)

控制超时

timeOutCtx, _ = context.WithTimeout(ctx, 2*time.Second)
go func(){
    doSomething(timeOutCtx)
}

任务取消

比如结合超时控制,做超时取消后的任务补偿

// doJobA 模拟一个可能会执行较长时间的任务
func doJobA(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("doJobA: 任务被取消,原因:", ctx.Err())
        return
    case <-time.After(3 * time.Second):
        fmt.Println("doJobA: 任务完成")
    }
}

// doJobB 模拟超时取消后的补偿任务
func doJobB() {
    fmt.Println("doJobB: 执行补偿任务")
}

func main() {
    // 创建一个基础上下文
    ctx := context.Background()
    // 创建一个带有超时的上下文
    timeOutCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()

    // 启动一个 goroutine 执行任务 doJobA
    go func() {
        defer cancel()
        doJobA(timeOutCtx)
    }()

    // 等待超时信号
    <-timeOutCtx.Done()

    // 检查上下文取消的原因
    if err := timeOutCtx.Err(); err == context.DeadlineExceeded {
        fmt.Println("超时触发,原因:", err)
        // 执行补偿任务
        doJobB()
    } else {
        fmt.Println("任务正常结束,未超时,无需补偿")
    }

    fmt.Println("主程序结束")
}