下面由golang教學專欄給大家詳解Golang的context,希望對需要的朋友有幫助!
是的,今天本來還想出去玩的。買了動車票,然後又睡過頭了。 。沒辦法,可能是天意,只好總結golang的context,希望能與context之間做個了斷。
公司裡頭大家寫各種服務,必須需要將Context作為第一個參數,剛開始以為主要用於全鏈路排查追蹤。但隨著接觸多了,原來不只如此。
在go的1.7之前,context還是非編制的(包golang.org/x /net/context中),golang團隊發現context這個東西還挺好用的,很多地方也都用到了,就把它收編了,1.7版本正式進入標準庫。
context常用的使用姿勢:
1.web程式設計中,一個請求對應多個goroutine之間的資料互動
2.逾時控制
3.上下文控制
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
這個就是Context的底層資料結構,來分析下:
欄位 | 意思 |
---|---|
Deadline | 回傳一個time.Time,表示目前Context應該結束的時間,ok則表示有結束時間 |
Done | 當Context被取消或超時時候傳回的一個close的channel,告訴給context相關的函數要停止目前工作然後回傳了。 (這個有點像是全域廣播) |
Err | context被取消的原因 |
context實作共享資料儲存的地方,是協程安全的(還記得之前有說過map是不安全的?所以遇到map的結構,如果不是sync.Map,需要加鎖來進行操作) |
// 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實現,來供大家玩
結構體 | 作用 | |
---|---|---|
type emptyCtx int | 完全空的Context,實作的函數也都是回傳nil,只是實作了Context的介面 | |
type cancelCtx struct { | Context mu ## children map[canceler]struct{} err error } 繼承自Context,同時也實現了canceler接口 |
timerCtx |
cancelCtx | timer *time.Timer // Under cancelCtx.mu. 繼承自 cancelCtx ,增加了timeout機制 | valueCtxtype valueCtx struct { Context | key, val # Context
儲存鍵值對的資料 |
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 := 0 go func() { for { //time.Sleep(time.Second) select { case <-ctx.Done(): fmt.Printf("耗时 %d 秒,吃了 %d 个汉堡 \n", t, n) return case c <- n: incr := rand.Intn(5) n += incr if 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 passed return 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 := 0 for { select { case <-ctx.Done(): fmt.Println("stop \n") return default: incr := rand.Intn(5) n += incr fmt.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} } 实战场景: 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的詳細內容。更多資訊請關注PHP中文網其他相關文章!