golang context機制

什麼是Context

Context一般被譯做上下文,是一個比較抽象的概念。通常理解爲程序單元的一個運行狀態、現場、快照,而翻譯中上下文又很好地詮釋了其本質。 html

每一個Goroutine在執行前,都要先知道程序當前的執行狀態,一般將這些執行狀態封裝在一個Context變量中,傳遞給要執行的Goroutine中。上下文則幾乎已經成爲傳遞與請求同生存週期變量的標準方法。在網絡變成下,當接收到一個網絡請求Request,處理Request時,咱們可能須要開啓不一樣的Goroutine來獲取數據和執行程序邏輯,即一個請求Request,會在多個Goroutine中處理。而這些Goroutine可能須要共享Request的一些信息;同時當Request被取消或者超時的時候,全部從這個Request建立的全部Goroutine也應該被結束。golang

context包

Go的標準庫[golang.org/x/net/context] web

context經常使用的使用姿式:
1.web編程中一個請求對應多個Goroutine之間的數據交互
2.超時控制
3.上下文控制 編程

context的底層結構安全

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
  • Deadline 返回一個time.Time,表示當前Context應該結束的時間,ok則表示有結束時間。
  • Done 當Context被取消或者超時的時候返回一個close的channel,告訴給context相關的函數要中止當前工做,而後返回了
  • Value context實現共享數據存儲的地方,是協程安全的

庫裏提供了4個Context實現網絡

# 徹底空的Context,實現的函數也都是返回nil,僅僅只是實現了Context的接口
type emptyCtx int 

# 繼承自Context,同時也實現了canceler接口
type cancelCtx struct {
    Context
    mu    sync.Mutex
    done chan struct{}
    
    children map[canceler]struct{}
    err error
 }
 
 # 繼承自**cancelCtx**,增長了timeout機制 
 type timerCtx struct {  
    cancelCtx  
    timer \*time.Timer // Under cancelCtx.mu.  
    deadline time.Time  
} 

# 存儲鍵值對的數據
type valueCtx struct {  
    Context  
    key, val interface{}  
}

context的建立

爲了更方便的建立Context,包裏面定義了Background 來做爲全部Context的根,它是一個emptyCtx的實例。session

var ( 
    background = new(emptyCtx) 
    todo = new(emptyCtx) // 
)
func Background() Context { 
    return background 
}

你能夠認爲全部的Context是樹的結構,Background是樹的根,當任一Context被取消的時候,那麼繼承它的Context 都將被回收。函數

context實戰

  • WidhCancel

實現源碼:.net

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 個漢堡
  • 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.WidhDeadline(context.Background(), time.Now().Add(10))
    ctx, cancel := contet.WidhTimeout(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
  • WidthValue

實現源碼:

func WidhValue(parent Context, key, value interface{}) Context {
    if key == nil {
        panic("nil key")
    }
    if !reflect.TypeOf(key).Comparable() { //必須是可比較類型
        panic("key is not comparable")
    }
    return &valueCtx{parent, key, value}
}

實戰場景:
攜帶關鍵信息,爲全鏈路提供線索,好比接入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

參考

https://blog.csdn.net/u011957...
https://www.cnblogs.com/zhang...