go Context在Go1.7引入标准库,它是Goroutine的上下文,包含其运行状态、环境、现场等信息
context
的作用就是在不同的goroutine
之间同步请求特定的数据、取消信号以及处理请求的截止日期。
Context接口 1 2 3 4 5 6 type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key interface {}) interface {} }
Deadline()
返回当前Context被取消的时间,即完成工作的截止时间
Done()
返回一个Channel,会在当前工作完成或者上下文被取消后关闭,多次调用只会返回同一个Channel
Err()
如果当前Context被取消返回Canceled
如果当前Context超时返回DeadlineExceeded
Value(key)
从Context中返回键对应的值,对同一个上下文传入相同的key返回相同的结果
仅用于传递跨API和进程间跟请求域的数据
这个接口主要被三个类继承实现,分别是emptyCtx
、ValueCtx
、cancelCtx
,采用匿名接口的写法,这样可以对任意实现了该接口的类型进行重写
根context Background()
和TODO()
是Go内置的函数,返回实现了Context接口的background和todo实例
本质上,这两个都是emptyCtx类型,是不可取消、无截止时间、无任何值的Context
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 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 }func (e *emptyCtx) String () string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }var ( background = new (emptyCtx) todo = new (emptyCtx) )
With函数 上面的两种方式是创建根context
,不具备任何功能,具体实践还是要依靠context
包提供的With
系列函数来进行派生
1 2 3 4 func WithCancel (parent Context) (ctx Context, cancel CancelFunc) func WithDeadline (parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue (parent Context, key, val interface {}) Context
WithValue 案例 e.g. 在日常开发中需要一个trace_id能够串联所有的日志,通过WithValue来创建一个携带trace_id的context,然后不断地传下去,打印日志时输出
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 package mainimport ( "context" "fmt" "strings" "time" "github.com/google/uuid" )const ( KEY = "trace_id" )func NewRequestID () string { return strings.Replace(uuid.New().String(), "-" , "" , -1 ) }func NewContextWithTraceID () context .Context { ctx := context.WithValue(context.Background(), KEY, NewRequestID()) return ctx }func GetContextValue (ctx context.Context, key string ) string { val, ok := ctx.Value(key).(string ) if !ok { return "" } return val }func PrintLog (ctx context.Context, message string ) { fmt.Printf("%s|info|trace_id=%s|%s" , time.Now().Format("2022-04-18 21:04:05" ) , GetContextValue(ctx, KEY), message) }func Run (ctx context.Context) { PrintLog(ctx, "running.........." ) }func main () { Run(NewContextWithTraceID()) }
1 2022 -04 -18 21 :23 :46.3088448 +0800 CST m=+0.002543101 |info|trace_id=658 abda3ae6f486c996d20fd01218bcb|running..........
通过context.Background
创建了携带trace_id的ctx,其派生的任何context都会获取到该值
源码分析 WithValue重写了Value()
1 2 3 4 5 6 7 8 9 10 11 12 13 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} }
1 2 3 4 5 type valueCtx struct { Context key, val interface {} }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (c *valueCtx) String () string { return contextName(c.Context) + ".WithValue(type " + reflectlite.TypeOf(c.key).String() + ", val " + stringify(c.val) + ")" }func (c *valueCtx) Value (key interface {}) interface {} { if c.key == key { return c.val } return c.Context.Value(key) }
注意事项
不建议使用context值传递关键参数,关键参数应显示地声明出来,不要隐式处理。context中适合携带签名、trace_id等值
为避免context因多个包同时使用context冲突,key应采用内置类型
context传递的数据中key、value都是interface类型(编译器无法确定类型),所以应该在类型断言时保证程序的健壮性
WithTimeout, WithDeadline 这两个函数都是用来超时控制 的,避免服务端长时间响应消耗资源。当一次请求达到超时时间时就会取消,不会继续传递
WithTimeout, WithDeadline两个函数的作用是一样的,区别在于传递的时间参数 不同: WithTimeout将持续时间 作为参数输入而不是时间对象
案例
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 package mainimport ( "context" "time" "fmt" )func NewContextWithTimeout () (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 3 * time.Second) }func HttpHandler () { ctx, cancel := NewContextWithTimeout() defer cancel() deal(ctx) }func deal (ctx context.Context) { for i := 0 ; i <10 ; i++ { time.Sleep(1 * time.Second) select { case <- ctx.Done(): fmt.Println(ctx.Err()) return default : fmt.Printf("deal time is %d\n" , i) } } }func main () { HttpHandler() }
1 2 3 4 deal time is 0 deal time is 1 context deadline exceeded
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 package mainimport ( "context" "time" "fmt" )func NewContextWithTimeout () (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 3 * time.Second) }func HttpHandler () { ctx, cancel := NewContextWithTimeout() defer cancel() deal(ctx, cancel) }func deal (ctx context.Context, cancel context.CancelFunc) { for i := 0 ; i <10 ; i++ { time.Sleep(1 * time.Second) select { case <- ctx.Done(): fmt.Println(ctx.Err()) return default : fmt.Printf("deal time is %d\n" , i) cancel() } } }func main () { HttpHandler() }
1 2 3 deal time is 0 context canceled
withtimout返回的context会在超时时间到时自动取消,同时也可以使用返回的cancelFunc手动取消
源码分析 WithTimeout内部调用WithDeadline方法
1 2 3 func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
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 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) } }
WithCancel 案例 日常开发中往往会开多个goroutine去处理事情,我们可以使用WithCancel来衍生一个context传递到不同的goroutine中,通过cancel来控制其停止运行
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 package mainimport ( "context" "time" "fmt" )func main () { ctx, cancel := context.WithCancel(context.Background()) go Speak(ctx) time.Sleep(10 * time.Second) cancel() time.Sleep(1 * time.Second) }func Speak (ctx context.Context) { for range time.Tick(time.Second) { select { case <- ctx.Done(): fmt.Println("shut up" ) return default : fmt.Println("balahlahlahlahlah" ) } } }
1 2 3 4 5 6 7 8 9 10 11 balahlahlahlahlah balahlahlahlahlah balahlahlahlahlah balahlahlahlahlah balahlahlahlahlah balahlahlahlahlah balahlahlahlahlah balahlahlahlahlah balahlahlahlahlah shut up
源码分析 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 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) } }func newCancelCtx (parent Context) cancelCtx { return cancelCtx{Context: parent} }type cancelCtx struct { Context mu sync.Mutex done chan struct {} children map [canceler]struct {} err error }type canceler interface { cancel(removeFromParent bool , err error) Done() <-chan struct {} }
cancelCtx实现了Done()和Value()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func (c *cancelCtx) Done () <-chan struct {} { c.mu.Lock() if c.done == nil { c.done = make (chan struct {}) } d := c.done c.mu.Unlock() return d func (c *cancelCtx) Value (key interface {}) interface {} { if key == &cancelCtxKey { return c } return c.Context.Value(key) }
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 func propagateCancel (parent Context, child canceler) { done := parent.Done() if done == nil { return } select { case <-done: child.cancel(false , parent.Err()) return default : } if p, ok := parentCancelCtx(parent); ok { p.mu.Lock() if p.err != nil { child.cancel(false , p.err) } else { if p.children == nil { p.children = make (map [canceler]struct {}) } p.children[child] = struct {}{} } p.mu.Unlock() } else { atomic.AddInt32(&goroutines, +1 ) go func () { select { case <-parent.Done(): child.cancel(false , parent.Err()) case <-child.Done(): } }() } }
cancel方法
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 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 d, _ := c.done.Load().(chan struct {}) if d == nil { c.done.Store(closedchan) } else { close (d) } for child := range c.children { child.cancel(false , err) } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) } }