Golang context包入门
Golang context包入门
转自:http://studygolang.com/articles/9624
概述Golang 的 context Package 提供了一种简洁又强大方式来管理 goroutine 的生命周期,同时提供了一种 Requst-Scope K-V Store。但是对于新手来说,Context 的概念不算非常的直观,这篇文章来带领大家了解一下 Context 包的基本作用和使用方法。 1. 包的引入在 go1.7 及以上版本 context 包被正式列入官方库中,所以我们只需要 2. Context 基本数据结构Context interfaceContext interface 是最基本的接口 type Context interface { Deadline() (deadline time.Time,ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
canceler interfacecanceler interface 定义了提供 cancel 函数的 context,当然要求数据结构要同时实现 Context interface type canceler interface { cancel(removeFromParent bool,err error) Done() <-chan struct{} } Structs除了以上两个 interface 之外,context 包中还定义了若干个struct,来实现上面的 interface
3. Context 实例化和派生Context 只定义了 interface,真正使用时需要实例化,官方首先定义了一个 emptyCtx struct 来实现 Context interface,然后提供了 // An emptyCtx is never canceled,has no values,and has no deadline. It is not // struct{},since vars of this type must have distinct addresses. 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) ) func Background() Context { return background } Backgroud() 生成的 emptyCtx 实例是不能取消的,因为 WithCancel调用 func WithCancel(parent Context) (ctx Context,cancel CancelFunc) { c := newCancelCtx(parent) propagateCancel(parent,&c) return &c,func() { c.cancel(true,Canceled) } } // newCancelCtx returns an initialized cancelCtx. func newCancelCtx(parent Context) cancelCtx { return cancelCtx{ Context: parent,done: make(chan struct{}),} } WithTimeout调用 func WithTimeout(parent Context,timeout time.Duration) (Context,CancelFunc) { return WithDeadline(parent,time.Now().Add(timeout)) func WithDeadline(parent Context,deadline time.Time) (Context,CancelFunc) { if cur,ok := parent.Deadline(); ok && cur.Before(deadline) { // The current deadline is already sooner than the new one. return WithCancel(parent) } c := &timerCtx{ cancelCtx: newCancelCtx(parent),deadline: deadline,} propagateCancel(parent,c) d := deadline.Sub(time.Now()) if d <= 0 { c.cancel(true,DeadlineExceeded) // deadline has already passed return c,Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(d,func() { c.cancel(true,DeadlineExceeded) }) } return c,Canceled) } } 在 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,val} } 4. 实际用例(1)超时结束示例我们起一个本地的 http serice,名字叫"lazy",这个 http server 会随机的发出一些慢请求,要等6秒以上才返回,我们使用这个程序来模拟我们的被调用方 hang 住的情况 package main import ( "net/http" "math/rand" "fmt" "time" ) func lazyHandler(w http.ResponseWriter,req *http.Request) { ranNum := rand.Intn(2) if ranNum == 0 { time.Sleep(6 * time.Second) fmt.Fprintf(w,"slow response,%dn",ranNum) fmt.Printf("slow response,ranNum) return } fmt.Fprintf(w,"quick response,ranNum) fmt.Printf("quick response,ranNum) return } func main() { http.HandleFunc("/",lazyHandler) http.ListenAndServe(":9200",nil) } 然后我们写一个主动调用的 http service,他会调用我们刚才写的"lazy",我们使用 context,来解决超过2秒的慢请求问题,如下代码: package main import ( "context" "net/http" "fmt" "sync" "time" "io/ioutil" ) var ( wg sync.WaitGroup ) type ResPack struct { r *http.Response err error } func work(ctx context.Context) { tr := &http.Transport{} client := &http.Client{Transport: tr} defer wg.Done() c := make(chan ResPack,1) req,_ := http.NewRequest("GET","http://localhost:9200",nil) go func() { resp,err := client.Do(req) pack := ResPack{r: resp,err: err} c <- pack }() select { case <-ctx.Done(): tr.CancelRequest(req) <-c fmt.Println("Timeout!") case res:= <-c: if res.err != nil { fmt.Println(res.err) return } defer res.r.Body.Close() out,_ := ioutil.ReadAll(res.r.Body) fmt.Printf("Server Response: %s",out) } return } func main() { ctx,cancel := context.WithTimeout(context.Background(),2 * time.Second) defer cancel() wg.Add(1) go work(ctx) wg.Wait() fmt.Println("Finished") } 在 main 函数中,我们定义了一个超时时间为2秒的 context,传给真正做事的 (2)使用 WithValue 制作生成 Request ID 中间件在 Golang1.7 中, package main import ( "net/http" "context" "fmt" ) const requestIDKey = "rid" func newContextWithRequestID(ctx context.Context,req *http.Request) context.Context { reqID := req.Header.Get("X-Request-ID") if reqID == "" { reqID = "0" } return context.WithValue(ctx,requestIDKey,reqID) } func requestIDFromContext(ctx context.Context) string { return ctx.Value(requestIDKey).(string) } func middleWare(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter,req *http.Request) { ctx := newContextWithRequestID(req.Context(),req) next.ServeHTTP(w,req.WithContext(ctx)) }) } func h(w http.ResponseWriter,req *http.Request) { reqID := requestIDFromContext(req.Context()) fmt.Fprintln(w,"Request ID: ",reqID) return } func main() { http.Handle("/",middleWare(http.HandlerFunc(h))) http.ListenAndServe(":9201",nil) } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |