对服务的传入请求应创建一个上下文,而对服务器的传出调用应接受一个上下文.它们之间的函数调用链必须传播Context 上下文对象.
context 包定义了 Context 上下文类型,并可以选择使用 WithCancel,WithDeadline,WithTimeout 和 WithValue 等方法创建派生 Context 上下文对象.如下:
1 2 3 4 func WithCancel (parent Context) (ctx Context, cancel CancelFunc) func WithDeadline (parent Context, d time.Time) (Context, CancelFunc) func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue (parent Context, key, val interface {}) Context
WithCancel,WithDeadline 和 WithTimeout 函数传入父级 Context 对象,并返回派生的子级 Context 和CancelFunc.调用 CancelFunc 会取消该子级 Context 对象,删除父级对子级的引用,并停止所有关联的计时器.没有调用 CancelFunc 会使子级及其子级上下文对象泄漏,直到父代被取消或计时器触发为止.
上下文的使用遵循如下规则:
不要将上下文存储在结构体中,而是将其作为参数传递给需要它的函数
不要传递 nil 上下文.如果不确定使用哪个上下文,则使用 context.TODO
可以将相同的上下文传递给在不同 goroutine 中运行的函数.上下文对于由多个 goroutine 同时使用是安全的.
类型及函数定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type CancelFunc func () type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key interface {}) interface {} }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func WithCancel (parent Context) (ctx Context, cancel CancelFunc) func WithDeadline (parent Context, d time.Time) (Context, CancelFunc) func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc) func Background () Context func func TODO () Context func WithValue (parent Context, key, val interface {}) Context
Context Context 定义如下,包含了过期日期,取消信号和键值对等 API.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key interface {}) interface {} }
WithCancel() 首先看如下示例,创建了只能显式取消的子上下文对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import ( "context" "fmt" "sync" ) func ctxCancel () { var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) wg.Add(1 ) go func (ctx context.Context) { defer wg.Done() select { case <-ctx.Done(): fmt.Println("ctx.done()" , ctx.Err()) case <-time.After(time.Second): fmt.Println("time out" , ctx.Err()) } }(ctx) cancel() wg.Wait() }
WithCancel() 方法如下,返回 cancelCtx 对象与 cancelFunc 方法.
1 2 3 4 5 6 7 8 9 func WithCancel (parent Context) (ctx Context, cancel CancelFunc) { if parent == nil { panic ("cannot create context from nil parent" ) } c := newCancelCtx(parent) propagateCancel(parent, &c) return &c, func () { c.cancel(true , Canceled) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 type cancelCtx struct { Context mu sync.Mutex done chan struct {} children map [canceler]struct {} err error } func (c *cancelCtx) cancel (removeFromParent bool , err error) { if err == nil { panic ("context: internal error: missing cancel error" ) } c.mu.Lock() if c.err != nil { c.mu.Unlock() return } c.err = err if c.done == nil { c.done = closedchan } else { close (c.done) } for child := range c.children { child.cancel(false , err) } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) } }
WithTimeout() 与 WithDeadline() 首先看如下示例,创建了带有超时时间与过期时间的子上下文对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import ( "context" "fmt" "sync" "time" ) func ctxTimeout () { var wg sync.WaitGroup ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) defer cancel() wg.Add(1 ) go func (ctx context.Context) { defer wg.Done() select { case <-ctx.Done(): fmt.Println("ctx.done()" , ctx.Err()) case <-time.After(time.Second): fmt.Println("time out" , ctx.Err()) } }(ctx) wg.Wait() } func ctxDeadline () { var wg sync.WaitGroup ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond)) defer cancel() wg.Add(1 ) go func (ctx context.Context) { defer wg.Done() select { case <-ctx.Done(): fmt.Println("ctx.done()" , ctx.Err()) case <-time.After(time.Second): fmt.Println("time out" , ctx.Err()) } }(ctx) wg.Wait() }
可以看到 WithTimeout() 函数其实是调用了 WithDeadline() 函数.返回的上下对象为 timerCtx.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) } func WithDeadline (parent Context, d time.Time) (Context, CancelFunc) { if parent == nil { panic ("cannot create context from nil parent" ) } if cur, ok := parent.Deadline(); ok && cur.Before(d) { return WithCancel(parent) } c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: d, } propagateCancel(parent, c) dur := time.Until(d) if dur <= 0 { c.cancel(true , DeadlineExceeded) return c, func () { c.cancel(false , Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(dur, func () { c.cancel(true , DeadlineExceeded) }) } return c, func () { c.cancel(true , Canceled) } }
timerCtx 结构体如下.相比于 cancelCtx,该结构体包含了计时器 timer 与 deadline 过期时间.它调用了 cancelCtx.cancel(),并停止了计时器.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type timerCtx struct { cancelCtx timer *time.Timer deadline time.Time } func (c *timerCtx) cancel (removeFromParent bool , err error) { c.cancelCtx.cancel(false , err) if removeFromParent { removeChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock() }
WithValue() 首先看如下示例,创建了带有父上下文中键值对的子上下文对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import ( "context" "fmt" "sync" ) type User struct { name string age int } func ctxValue () { var wg sync.WaitGroup user := User{ name: "tom" , age: 18 , } ctx := context.WithValue(context.Background(), "user" , user) wg.Add(1 ) go func (ctx context.Context) { defer wg.Done() if v, ok := ctx.Value("user" ).(User); ok { fmt.Printf("user is %v" , v) } }(ctx) wg.Wait() }
可以看到 WithValue() 函数返回的上下对象为 valueCtx,且不提供相关的 cancelFunc 方法.
1 2 3 4 5 6 7 8 9 10 11 12 func WithValue (parent Context, key, val interface {}) Context { if parent == nil { panic ("cannot create context from nil parent" ) } if key == nil { panic ("nil key" ) } if !reflectlite.TypeOf(key).Comparable() { panic ("key is not comparable" ) } return &valueCtx{parent, key, val} }
valueCtx 结构体如下,其仅在 Context 的基础上添加了 key, val interface{} 成员对象,用于将键值对传入到子上下文对象中.其中其 Value() 方法会首先在当前上下文中查找指定键的值,找到则返回,否则在其父上下文中进行查找.
1 2 3 4 5 6 7 8 9 10 11 12 type valueCtx struct { Context key, val interface {} } func (c *valueCtx) Value (key interface {}) interface {} { if c.key == key { return c.val } return c.Context.Value(key) }
需要注意的是:
WithValue() 函数传入的 key 必须是可比较的,并且不能为字符串类型或任何其他内置类型,以避免在上下文之间发生冲突.
用户应定义自己的 key 类型,key 通常是具体的的 struct{} 结构体.