本文主要是介绍由浅入深聊聊Golang的context,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
是的,今天本来还想出去玩的。买了动车票,然后又睡过头了。。没办法,可能是天意,只好总结一下golang的context,希望能与context之间做一个了断。
公司里头大家写各种服务,必须需要将Context作为第一个参数,刚开始以为主要用于全链路排查跟踪。但是随着接触多了,原来不止于此。
正文
1.context详解
1.1 产生背景
在go的1.7之前,context还是非编制的(包golang.org/x/net/context中),golang团队发现context这个东西还挺好用的,很多地方也都用到了,就把它收编了,1.7版本正式进入标准库。
context常用的使用姿势:
1.web编程中,一个请求对应多个goroutine之间的数据交互
2.超时控制
3.上下文控制
1.2 context的底层结构
type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key interface{}) interface{}
}
这个就是Context的底层数据结构,来分析下:
字段 | 含义 |
---|---|
Deadline | 返回一个time.Time,表示当前Context应该结束的时间,ok则表示有结束时间 |
Done | 当Context被取消或者超时时候返回的一个close的channel,告诉给context相关的函数要停止当前工作然后返回了。(这个有点像全局广播) |
Err | context被取消的原因 |
Value | context实现共享数据存储的地方,是协程安全的(还记得之前有说过map是不安全的?所以遇到map的结构,如果不是sync.Map,需要加锁来进行操作) |
同时包中也定义了提供cancel功能需要实现的接口。这个主要是后文会提到的“取消信号、超时信号”需要去实现。
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {cancel(removeFromParent bool, err error)Done() <-chan struct{}
}
那么库里头提供了4个Context实现,来供大家玩耍
实现 | 结构体 | 作用 |
---|---|---|
emptyCtx | type emptyCtx int | 完全空的Context,实现的函数也都是返回nil,仅仅只是实现了Context的接口 |
cancelCtx | type cancelCtx struct { Context mu sync.Mutex done chan struct{} children map[canceler]struct{} err error } | 继承自Context,同时也实现了canceler接口 |
timerCtx | type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time } | 继承自cancelCtx,增加了timeout机制 |
valueCtx | type valueCtx struct { Context key, val interface{} } | 存储键值对的数据 |
1.3 context的创建
为了更方便的创建Context,包里头定义了Background来作为所有Context的根,它是一个emptyCtx的实例。
var (background = new(emptyCtx)todo = new(emptyCtx) //
)func Background() Context {return background
}
你可以认为所有的Context是树的结构,Background是树的根,当任一Context被取消的时候,那么继承它的Context 都将被回收。
2.context实战应用
2.1 WithCancel
实现源码:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := newCancelCtx(parent)propagateCancel(parent, &c)return &c, func() { c.cancel(true, Canceled) }
}
实战场景:
执行一段代码,控制执行到某个度的时候,整个程序结束。
吃汉堡比赛,奥特曼每秒吃0-5个,计算吃到10的用时
实战代码:
func main() {ctx, cancel := context.WithCancel(context.Background())eatNum := chiHanBao(ctx)for n := range eatNum {if n >= 10 {cancel()break}}fmt.Println("正在统计结果。。。")time.Sleep(1 * time.Second)
}func chiHanBao(ctx context.Context) <-chan int {c := make(chan int)// 个数n := 0// 时间t := 0go func() {for {//time.Sleep(time.Second)select {case <-ctx.Done():fmt.Printf("耗时 %d 秒,吃了 %d 个汉堡 \n", t, n)returncase c <- n:incr := rand.Intn(5)n += incrif n >= 10 {n = 10}t++fmt.Printf("我吃了 %d 个汉堡\n", n)}}}()return c
}
输出:
我吃了 1 个汉堡
我吃了 3 个汉堡
我吃了 5 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
正在统计结果。。。
耗时 6 秒,吃了 10 个汉堡
2.2 WithDeadline & WithTimeout
实现源码:
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {if cur, ok := parent.Deadline(); ok && cur.Before(d) {// The current deadline is already sooner than the new one.return WithCancel(parent)}c := &timerCtx{cancelCtx: newCancelCtx(parent),deadline: d,}propagateCancel(parent, c)dur := time.Until(d)if dur <= 0 {c.cancel(true, DeadlineExceeded) // deadline has already passedreturn c, func() { c.cancel(true, 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) }
}func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {return WithDeadline(parent, time.Now().Add(timeout))
}
实战场景:
执行一段代码,控制执行到某个时间的时候,整个程序结束。
吃汉堡比赛,奥特曼每秒吃0-5个,用时10秒,可以吃多少个
实战代码:
func main() {// ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10))ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)chiHanBao(ctx)defer cancel()
}func chiHanBao(ctx context.Context) {n := 0for {select {case <-ctx.Done():fmt.Println("stop \n")returndefault:incr := rand.Intn(5)n += incrfmt.Printf("我吃了 %d 个汉堡\n", n)}time.Sleep(time.Second)}
}
输出:
我吃了 1 个汉堡
我吃了 3 个汉堡
我吃了 5 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
我吃了 13 个汉堡
我吃了 13 个汉堡
我吃了 13 个汉堡
我吃了 14 个汉堡
我吃了 14 个汉堡
stop
2.3 WithValue
实现源码:
func WithValue(parent Context, key, val interface{}) Context {if key == nil {panic("nil key")}if !reflect.TypeOf(key).Comparable() {panic("key is not comparable")}return &valueCtx{parent, key, val}
}
实战场景:
携带关键信息,为全链路提供线索,比如接入elk等系统,需要来一个trace_id,那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)fmt.Println(ok)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)
}
输出:
traceID: 88888888 -session: 1
3.context建议
不多就一个。
Context要是全链路函数的第一个参数。
func myTest(ctx context.Context) {...
}
(写好了竟然忘记发送了。。。汗)
如果你觉得有收获~可以关注我的公众号【咖啡色的羊驼】~第一时间收到我的分享和知识梳理~
这篇关于由浅入深聊聊Golang的context的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!