Context 包的核心就是 Context 接口,其定义如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
其中:
- Deadline 方法需要返回当前 Context 被取消的时间,也就是完成工作的截止时间(deadline);
- Done 方法需要返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个Channel;
- Err 方法会返回当前 Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值:
- 如果当前 Context 被取消就会返回 Canceled 错误;
- 如果当前 Context 超时就会返回 DeadlineExceeded 错误;
- Value 方法会从 Context 中返回键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法仅用于传递跨 API 和进程间跟请求域的数据。
context.Background()
Background() 主要用于 main 函数、初始化以及测试代码中,作为 Context 这个树结构的最顶层的 Context,也就是根 Context。
WithCancel
WithCancel 的函数签名如下:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
ctx .Done表示上下文的通道,用于接受取消信号。
WithCancel 返回带有新 Done 通道的父节点的副本,当调用返回的 cancel 函数或当关闭父上下文的 Done 通道时,将关闭返回上下文的 Done 通道,无论先发生什么情况。
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
for{
select {
case <-ctx.Done():
return
}
}
}()
fmt.Println("睡眠2秒")
defer cancel()
time.Sleep(time.Second*2)
}
WithDeadline
WithDeadline 的函数签名如下:
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
当截止日过期时,当调用返回的 cancel 函数时,或者当父上下文的 Done 通道关闭时,返回上下文的 Done 通道将被关闭,以最先发生的情况为准。
package main
import (
"context"
"fmt"
"time"
)
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// 尽管ctx会过期,但在任何情况下调用它的cancel函数都是很好的实践。
// 如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间。
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
WithTimeout
WithTimeout 的函数签名如下:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
与WithDeadline 的区别:WithDeadline 第二个参数是截止时间,WithTimeout第二个参数是超时时间
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 传递带有超时的上下文
// 告诉阻塞函数在超时结束后应该放弃其工作。
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // 终端输出"context deadline exceeded"
}
}
WithValue
WithValue 函数能够将请求作用域的数据与 Context 对象建立关系。函数声明如下:
func WithValue(parent Context, key, val interface{}) Context
WithValue 函数接收 context 并返回派生的 context,其中值 val 与 key 关联,并通过 context 树与 context 一起传递。这意味着一旦获得带有值的 context,从中派生的任何 context 都会获得此值。不建议使用 context 值传递关键参数,函数应接收签名中的那些值,使其显式化。
所提供的键必须是可比较的,并且不应该是 string 类型或任何其他内置类型,以避免使用上下文在包之间发生冲突。WithValue 的用户应该为键定义自己的类型,为了避免在分配给接口{ } 时进行分配,上下文键通常具有具体类型 struct{}。或者,导出的上下文关键变量的静态类型应该是指针或接口。
package main
import (
"context"
"fmt"
)
func main() {
type favContextKey string // 定义一个key类型
// f:一个从上下文中根据key取value的函数
f := func(ctx context.Context, k favContextKey) {
if v := ctx.Value(k); v != nil {
fmt.Println("found value:", v)
return
}
fmt.Println("key not found:", k)
}
k := favContextKey("language")
// 创建一个携带key为k,value为"Go"的上下文
ctx := context.WithValue(context.Background(), k, "Go")
f(ctx, k)
f(ctx, favContextKey("color"))
}
在真正使用传值的功能时我们也应该非常谨慎,不能将请求的所有参数都使用 Context 进行传递,这是一种非常差的设计,比较常见的使用场景是传递请求对应用户的认证令牌以及用于进行分布式追踪的请求 ID。