標籤: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(設計及分析)