Context
context
包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,使我们在被调用程序单元的外部,通过设置ctx变量值,将过期或撤销这些信号传递给被调用的程序单元。在网络编程中,若存在A调用B的API, B再调用C的API,若A调用B取消,那也要取消B调用C,通过在A,B,C的API调用之间传递Context
,以及判断其状态,就能解决此问题,这是为什么gRPC的接口中带上ctx context.Context
参数的原因之一。
Context结构
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
-
Done 方法在Context被取消或超时时返回一个close的channel,close的channel可以作为广播通知,告诉给context相关的函数要停止当前工作然后返回。
-
Err方法返回context为什么被取消。
-
Deadline返回context何时会超时。
-
Value返回context相关的数据。
Context的衍生
Golang 内置了两个 Context 接口实现,context.Background 和 context.TODO,作为最顶层的 Parent Context,即 Root Context。
- Background:主要用于 main() 函数、初始化以及测试代码中,作为 Context Tree 的 Root Context。
- TODO:如果我们不知道该使用什么类型 Context 的时候,可以使用这个。
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
两者本质上都是 emptyCtx 结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的 Context。
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
emptyCtx 实现了 Context 接口,但什么都没做,返回 nil 或者零值。
WithCancel
WithCancel返回一个继承的Context,这个Context在父Context的Done被关闭时关闭自己的Done通道,或者在自己被Cancel的时候关闭自己的Done。
WithCancel同时还返回一个取消函数cancel,这个cancel用于取消当前的Context。
示例:
func main() {
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // returning not to leak the goroutine
case dst <- n:
n++
}
}
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
WithDeadline
设置过期时间,指定什么时间取消context
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) //context deadline exceeded
}
}
WithTimeout
设置时间,指定多长时间后取消context
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()) // prints "context deadline exceeded"
}
}
WithValue
携带关键信息,为全链路提供线索。
func main() {
ctx := context.WithValue(context.Background(), "trace_id", "88888888")
// 携带session到后面的程序中去
ctx = context.WithValue(ctx, "session", 1)
process(ctx)
}
func process(ctx context.Context) {
session, ok := ctx.Value("session").(int)
if !ok {
fmt.Println("something wrong")
return
}
if session != 1 {
fmt.Println("session 未通过")
return
}
traceID := ctx.Value("trace_id").(string)
fmt.Println("traceID:", traceID, "-session:", session)
}
Context 使用原则
- 不要把 Context 作为结构体成员之一,要以参数的方式进行传递。
- 以 Context 作为形参的函数或方法,应该把 Context 作为第一个参数。
- 给一个函数或方法传递 Context 的时候,如果不知道传递什么,就使用 context.TODO。
- Context 的 Value 方法应该传递必要的数据。
- Context 是线程安全的,可以放心的在多个 Goroutine 中传递。
- 可以把一个 Context 实例传递给任意个 Gorotuine,对它执行 取消操作时,所有 Goroutine 都会接收到取消信号。