Golang context 包入門

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

概述

Golang 的 context Package 提供了一種簡潔又強大方式來管理 goroutine 的生命週期,同時提供了一種 Requst-Scope K-V Store。但是對於新手來說,Context 的概念不算非常的直觀,這篇文章來帶領大家瞭解一下 Context 包的基本作用和使用方法。

1. 包的引入

在 go1.7 及以上版本 context 包被正式列入官方庫中,所以我們只需要import "context"就可以了,而在 go1.6 及以下版本,我們要 import "golang.org/x/net/context"

2. Context 基本資料結構

Context interface

Context interface 是最基本的介面

type Context interface {    Deadline() (deadline time.Time, ok bool)    Done() <-chan struct{}    Err() error    Value(key interface{}) interface{}}
  • Deadline()返回一個time.Time,是當前 Context 的應該結束的時間,ok 表示是否有 deadline
  • Done()返回一個struct{}類型的唯讀 channel
  • Err()返回 Context 被取消時的錯誤
  • Value(key interface{}) 是 Context 內建的 K-V 儲存功能

canceler interface

canceler interface 定義了提供 cancel 函數的 context,當然要求資料結構要同時實現 Context interface

type canceler interface {    cancel(removeFromParent bool, err error)    Done() <-chan struct{}}

Structs

除了以上兩個 interface 之外,context 包中還定義了若干個struct,來實現上面的 interface

  • emptyCtx
    emptyCtx是空的Context,只實現了Context interface,只能作為 root context 使用。

    type emptyCtx int
  • cancelCtx
    cancelCtx繼承了Context並實現了cancelerinterface,從WithCancel()函數產生

    type cancelCtx struct {    Context    done chan struct{} // closed by the first cancel call.    mu       sync.Mutex    children map[canceler]bool // set to nil by the first cancel call    err      error             // set to non-nil by the first cancel call}
  • timerCtx
    timerCtx繼承了cancelCtx,所以也自然實現了Contextcanceler這兩個interface,由WithDeadline()函數產生

    type timerCtx struct {    cancelCtx    timer *time.Timer // Under cancelCtx.mu.deadline time.Time}
  • valueCtx
    valueCtx包含keyval field,可以儲存一對索引值對,由WithValue()函數產生

    type valueCtx struct {    Context    key, val interface{}}

3. Context 執行個體化和派生

Context 只定義了 interface,真正使用時需要執行個體化,官方首先定義了一個 emptyCtx struct 來實現 Context interface,然後提供了Backgroud()函數來便利的產生一個 emptyCtx 執行個體。
實現代碼如下

// 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 intfunc (*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 執行個體是不能取消的,因為emptyCtx沒有實現canceler interface,要正常取消功能的話,還需要對 emptyCtx 執行個體進行派生。常見的兩種派生用法是WithCancel()WithTimeout

WithCancel

調用WithCancel()可以將基礎的 Context 進行繼承,返回一個cancelCtx樣本,並返回一個函數,可以在外層直接調用cancelCtx.cancel()來取消 Context
代碼如下:

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

調用WithTimeout,需要傳一個逾時時間。來指定過多長時間後逾時結束 Context,原始碼中可以得知WithTimeoutWithDeadline的一層皮,WithDeadline傳的是具體的結束時間點,這個在工程中並不實用,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, func() { c.cancel(true, Canceled) }    }    c.mu.Lock()    defer c.mu.Unlock()    if c.err == nil {        c.timer = time.AfterFunc(d, func() {            c.cancel(true, DeadlineExceeded)        })    }    return c, func() { c.cancel(true, Canceled) }}

WithDeadline中,將 timeCtx.timer 掛上結束時的回呼函數,回呼函數的內容是調用cancel來結束 Context。

WithValue

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}}

4. 實際用例

(1)逾時結束樣本

我們起一個本地的 http serice,名字叫"lazy",這個 http server 會隨機的發出一些慢請求,要等6秒以上才返回,我們使用這個程式來類比我們的被呼叫者 hang 住的情況

package mainimport (    "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, %d\n", ranNum)        fmt.Printf("slow response, %d\n", ranNum)        return    }    fmt.Fprintf(w, "quick response, %d\n", ranNum)    fmt.Printf("quick response, %d\n", ranNum)    return}func main() {    http.HandleFunc("/", lazyHandler)    http.ListenAndServe(":9200", nil)}

然後我們寫一個主動調用的 http service,他會調用我們剛才寫的"lazy",我們使用 context,來解決超過2秒的慢請求問題,如下代碼:

package mainimport (    "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,傳給真正做事的work(),work接收到這個 ctx 的時候,需要等待 ctx.Done() 返回,因為 channel 關閉的時候,ctx.Done() 會受到空值,當 ctx.Done()返回時,就意味著 context 已經逾時結束,要做一些掃尾工作然後 return 即可。

(2)使用 WithValue 製作產生 Request ID 中介軟體

在 Golang1.7 中,"net/http"原生支援將Context嵌入到 *http.Request中,並且提供了http.Request.Conext()http.Request.WithContext()這兩個函數來建立一個 context 和 將 context 加入到一個http.Request執行個體中。下面的程式示範了一下利用WithValue()建立一個可以儲存 K-V 的 context,然後寫一個中介軟體來自動擷取 http頭部的 "X-Rquest-ID"值,加入到 context 中,使業務函數可以直接取到該值。

package mainimport (    "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)}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.