Golang的Context上下文是什么?

22-12-26 banq

Go中的上下文是一种跨越API边界携带请求范围的值和取消信号的方式。Go中经常使用它们来避免向一个函数传递多个参数,并取消一个函数的执行。

为什么使用上下文?
在Go中使用上下文的主要原因之一是为了避免向一个函数传递多个参数。请看下面的例子:

func doSomething(arg1 int, arg2 string, arg3 bool) {
    // Do some operation on the args
}


doSomething(1, "hello", true)


在这个例子中,我们要向doSomething函数传递三个参数。随着参数数量的增加,跟踪所有参数并确保它们以正确的顺序传递会变得很麻烦。

上下文提供了一种方法来分组相关的值,并将它们作为一个参数传递。下面是一个使用上下文的例子:

type MyContext struct {
    Arg1 int
    Arg2 string
    Arg3 bool
}

func doSomething(ctx context.Context) {
    myCtx := ctx.Value("my_context").(MyContext)
    arg1 := myCtx.Arg1
    arg2 := myCtx.Arg2
    arg3 := myCtx.Arg3
}

myCtx := MyContext{1, "hello", true}
ctx := context.WithValue(context.Background(), "my_context", myCtx)
doSomething(ctx)


在这个例子中,我们正在创建一个名为MyContext的结构,其中包含我们想要传递给doSomething函数的三个参数。然后我们使用 context.WithValue 函数来创建一个新的上下文,并将 MyContext 结构作为一个值。最后,我们将上下文传递给doSomething函数,并在该函数中从上下文中提取值。

取消一个函数的执行
在Go中使用上下文的另一个原因是要取消一个函数的执行。请看下面的例子。

func doSomething() {
    // Do something that takes a long time
}


go doSomething()


在这个例子中,我们在一个单独的goroutine中调用doSomething函数。如果我们想取消doSomething函数的执行,我们可以使用一个带有取消cancel函数的上下文。

func doSomething(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        default:
            time.sleep(6000)
        }
    }
}


ctx, cancel := context.WithCancel(context.Background())
go doSomething(ctx)
// Cancel the function execution after 5 seconds
time.AfterFunc(5 * time.Second, cancel)


在这个例子中,我们使用 context.WithCancel 函数来创建一个具有取消功能的上下文。然后我们将该上下文传递给doSomething函数,并使用选择语句来检查上下文的Done通道。如果Done通道被关闭,就意味着该上下文已被取消,我们从该函数中返回。

我们还使用time.AfterFunc函数在5秒后取消上下文。

超时
除了取消一个函数的执行之外,上下文还可以用来为一个函数设置超时。如果你想确保一个函数不会运行过长的时间,这是很有用的。

要设置一个超时,你可以使用context.WithTimeout函数。

ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
err := doSomething(ctx)
if err == context.DeadlineExceeded {
    // The function took too long
}


在这个例子中,我们使用context.WithTimeout函数来创建一个超时为5秒的上下文。然后我们将该上下文传递给doSomething函数,并检查该函数返回的错误。如果错误是context.DeadlineExceeded,这意味着该函数花费的时间超过了超时时间,我们可以相应地处理这个错误。

自定义Context
除了使用由上下文包提供的内置上下文外,你还可以创建你自己的自定义上下文。如果你想存储额外的值,或者你想给你的上下文添加自定义的行为,这可能很有用。

要创建一个自定义上下文,你可以定义一个实现context.Context接口的结构。

type MyContext struct {
    context.Context
    Arg1 int
    Arg2 string
}

func (ctx MyContext) Value(key interface{}) interface{} {
    if key == "arg1" {
        return ctx.Arg1
    }
    if key == "arg2" {
        return ctx.Arg2
    }
    return ctx.Context.Value(key)
}

在这个例子中,我们定义了一个名为MyContext的结构,它嵌入了context.Context接口并增加了两个额外的字段。Arg1 和 Arg2。我们还实现了context.Context接口的Value方法,以便通过上下文访问Arg1和Arg2的值。

为了使用自定义上下文,你可以创建一个新的MyContext实例并将其传递给一个函数。

myCtx := MyContext{context.Background(), 1, "hello"}
doSomething(myCtx)


总结
Context上下文是Go中一个有用的工具,可以跨越API边界携带请求范围的值和取消信号。它们可以用来避免向一个函数传递多个参数,也可以用来取消一个函数的执行。上下文也可用于设置超时,并创建具有额外行为或价值的自定义上下文。