Go語言Context(設計及分析)

來源:互聯網
上載者:User

標籤:ret   str   失效   取資料   概述   nil   sans   ase   round   

context簡單概述:

Go伺服器的每個請求都有自己的goroutine,而有的請求為了提高效能,會經常啟動額外的goroutine處理請求,當該請求被取消或逾時,該請求上的所有goroutines應該退出,防止資源流失。那麼context來了,它對該請求上的所有goroutines進行約束,然後進行取消訊號,逾時等操作。

而context優點就是簡潔的管理goroutines的生命週期。

context簡單使用:

接下來類比一個逾時繼續分析context

注意: 使用時遵循context規則1. 不要將 Context放入結構體,Context應該作為第一個參數傳   入,命名為ctx。2. 即使函數允許,也不要傳入nil的 Context。如果不知道用哪種    Context,可以使用context.TODO()。3. 使用context的Value相關方法,只應該用於在程式和介面中傳遞   和請求相關資料,不能用它來傳遞一些可選的參數4. 相同的 Context 可以傳遞給在不同的goroutine;Context 是   並發安全的。

使用net/http/pprof對goroutines進行查看:

package mainimport (    "context"    "fmt"    "net/http"    _ "net/http/pprof"    "time")func main() {    go http.ListenAndServe(":8080", nil)    ctx, _ := context.WithTimeout(context.Background(), (10 * time.Second))    go testA(ctx)    select {}}func testA(ctx context.Context) {    ctxA, _ := context.WithTimeout(ctx, (5 * time.Second))    ch := make(chan int)    go testB(ctxA, ch)    select {    case <-ctx.Done():        fmt.Println("testA Done")        return    case i := <-ch:        fmt.Println(i)    }}func testB(ctx context.Context, ch chan int) {    //類比讀取資料    sumCh := make(chan int)    go func(sumCh chan int) {        sum := 10        time.Sleep(10 * time.Second)        sumCh <- sum    }(sumCh)    select {    case <-ctx.Done():        fmt.Println("testB Done")        <-sumCh        return    //case ch  <- <-sumCh: 注意這樣會導致資源流失    case i := <-sumCh:        fmt.Println("send", i)        ch <- i    }}

類比資料庫讀取效率慢

從執行中和執行後的結果來看,我們完美的關閉了不需要的goroutine,


逾時

概述中提到:
當應用情境是由一個請求衍生出多個goroutine完成需求,那它們之間就需要滿足一定的約束關係,才能中止routine樹,逾時等操作。
那麼是如何到達約束關係?
簡單理解,Context 的調用以鏈式存在,通過WithXxx方法派生出新的 Context與當前父Context 關聯,當父 Context 被取消時,其派生的所有 Context 都將取消。


WithCancel派生約束關係圖

WithCancel派生簡單分析圖,接下裡我們進行源碼分析,進一步瞭解Context的約束關係(WithXxx方法派生大概與WithCancel相同)。

Context源碼分析:
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() 返回一個通道,當對Context進行撤銷或到期時,該通道就會關閉的,可以簡單認為它是關閉訊號。
Err() 當Done通道關閉後,Err會返回關閉的原因(如逾時,手動關閉)

Value(key interface{}) 一個 K-V 儲存的方法

canceler提供了cancal函數,同時要求資料結構實現Context

type canceler interface {    cancel(removeFromParent bool, err error)    Done() <-chan struct{}}
//預設錯誤var Canceled = errors.New("context canceled")var DeadlineExceeded = errors.New("context deadline exceeded")

實現Context的資料結構

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

兩個實現Context的空結構。

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

cancelCtx結構體繼承了Context,同時實現了canceler介面:

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             // 當被cancel時將會把err設定為非nil}func (c *cancelCtx) Done() <-chan struct{} {    return c.done}func (c *cancelCtx) Err() error {    c.mu.Lock()    defer c.mu.Unlock()    return c.err}func (c *cancelCtx) String() string {    return fmt.Sprintf("%v.WithCancel", c.Context)}func (c *cancelCtx) cancel(removeFromParent bool, err error) {    if err == nil {        panic("context: internal error: missing cancel error")    }    c.mu.Lock()    //timerCtx會頻繁使用這塊代碼,因為派生出來        //timerCtx全部指向同一個cancelCtx.    if c.err != nil {        c.mu.Unlock()        return // already canceled    }    c.err = err    //關閉c.done    close(c.done)    //依次遍曆c.children,每個child分別cancel    for child := range c.children {        // NOTE: acquiring the child‘s lock while holding parent‘s lock.        child.cancel(false, err)    }    c.children = nil    c.mu.Unlock()    //如果removeFromParent為true,則將c從其parent的children中刪除    if removeFromParent {        removeChild(c.Context, c)     }}//如果parent為valueCtx類型,將迴圈找最近parent//為CancelCtx類型的,找到就從父物件的children //map 中刪除這個child,否則返回nil(context.Background或者 context.TODO)func removeChild(parent Context, child canceler) {    p, ok := parentCancelCtx(parent)    if !ok {        return    }    p.mu.Lock()    if p.children != nil {        delete(p.children, child)    }    p.mu.Unlock()}

接下來分析Cancel相關代碼

type CancelFunc func()//WithCancel方法返回一個繼承parent的Context對//象,同時返回的cancel方法可以用來關閉當前//Context中的Done channelfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc) {    c := newCancelCtx(parent)    propagateCancel(parent, &c)    return &c, func() { c.cancel(true, Canceled) }}func newCancelCtx(parent Context) cancelCtx {    return cancelCtx{        Context: parent,        done:    make(chan struct{}),    }}func propagateCancel(parent Context, child canceler) {    if parent.Done() == nil {        return // parent is never canceled    }    if p, ok := parentCancelCtx(parent); ok {        p.mu.Lock()        if p.err != nil {            //如果找到的parent已經被cancel,            //則將方才傳入的child樹給cancel掉            child.cancel(false, p.err)        } else {           //否則, 將child節點直接添加到parent的children中           //(這樣就有了約束關係,向上的父親指標不變           //,向下的孩子指標可以直接使用 )            if p.children == nil {                p.children = make(map[canceler]bool)            }           p.children[child] = struct{}{}        }        p.mu.Unlock()    } else {      //如果沒有找到最近的可以被cancel的parent,      //則啟動一個goroutine,等待傳入的parent終止,      //並cancel傳入的child樹,或者等待傳入的child終結。      go func() {            select {            case <-parent.Done():                child.cancel(false, parent.Err())            case <-child.Done():            }        }()    }}//判斷parent是否為cancelCtx類型func parentCancelCtx(parent Context) (*cancelCtx, bool) {    for {        switch c := parent.(type) {        case *cancelCtx:            return c, true        case *timerCtx:            return &c.cancelCtx, true        case *valueCtx:            parent = c.Context        default:            return nil, false        }    }}

timerCtx 繼承cancelCtx的結構體,這種設計就可以避免寫重複代碼,提高複用

type timerCtx struct {    cancelCtx     timer *time.Timer  // Under cancelCtx.mu.  計時器    deadline time.Time}func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {    return c.deadline, true}func (c *timerCtx) String() string {    return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))}// 與cencelCtx有所不同,除了處理cancelCtx.cancel,// 還回對c.timer進行Stop(),並將c.timer=nilfunc (c *timerCtx) cancel(removeFromParent bool, err error) {    c.cancelCtx.cancel(false, err)    if removeFromParent {        // Remove this timerCtx from its parent cancelCtx‘s children.        removeChild(c.cancelCtx.Context, c)    }    c.mu.Lock()    if c.timer != nil {        c.timer.Stop()        c.timer = nil    }    c.mu.Unlock()}

timerCtx具體的兩個方法:

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {   //如果parent的deadline比新傳入的deadline早,則直接   //返回WithCancel,因為parent的deadline會先失效,而新的   //deadline根據不需要    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)    //檢查如果已經到期,則cancel新child樹    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 {        //沒有被cancel的話,就設定deadline之後cancel的計時器        c.timer = time.AfterFunc(d, func() {            c.cancel(true, DeadlineExceeded)        })    }    return c, func() { c.cancel(true, Canceled) }}// WithTimeout簡單暴力,直接把WithTimeout返回func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {    return WithDeadline(parent, time.Now().Add(timeout))}

valueCtx主要用來傳遞一些可比較操作的資料

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}type valueCtx struct {    Context    key, val interface{}}func (c *valueCtx) String() string {    return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)}func (c *valueCtx) Value(key interface{}) interface{} {    if c.key == key {        return c.val    }    return c.Context.Value(key)}

Go語言Context(設計及分析)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.