您好,登錄后才能下訂單哦!
這篇“Go中的Context怎么使用”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Go中的Context怎么使用”文章吧。
Context 接口定義如下
type Context interface { // Deadline returns the time when this Context will be canceled, if any. Deadline() (deadline time.Time, ok bool) // Done returns a channel that is closed when this Context is canceled // or times out. Done() <-chan struct{} // Err indicates why this context was canceled, after the Done channel // is closed. Err() error // Value returns the value associated with key or nil if none. Value(key any) any }
Deadline()
: 返回的第一個值是 截止時間,到了這個時間點,Context 會自動觸發 Cancel 動作。返回的第二個值是 一個布爾值,true 表示設置了截止時間,false 表示沒有設置截止時間,如果沒有設置截止時間,就要手動調用 cancel 函數取消 Context。
Done()
: 返回一個只讀的通道(只有在被cancel后才會返回),類型為 struct{}
。當這個通道可讀時,意味著parent context已經發起了取消請求,根據這個信號,開發者就可以做一些清理動作,退出goroutine。這里就簡稱信號通道吧!
Err()
:返回Context 被取消的原因
Value
: 從Context中獲取與Key關聯的值,如果沒有就返回nil
context
包提供了四種方法來創建context對象,具體方法如下:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {} func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {} func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {} func WithValue(parent Context, key, val any) Context {}
由以上方法可知:新的context對象都是基于父context對象衍生的.
WithCancel
:創建可以取消的Context
WithDeadline
: 創建帶有截止時間的Context
WithTimeout
:創建帶有超時時間的Context,底層調用的是WithDeadline
方法
WithValue
:創建可以攜帶KV型數據的Context
簡單的樹狀關系如下(實際可衍生很多中):
context
包默認提供了兩個根context 對象background
和todo
;看實現兩者都是由emptyCtx創建的,兩者的區別主要在語義上,
context.Background 是上下文的默認值,所有其他的上下文都應該從它衍生出來;
context.TODO 應該僅在不確定應該使用哪種上下文時使用
var ( background = new(emptyCtx) todo = new(emptyCtx) ) // Background 創建background context func Background() Context { return background } // TODO 創建todo context func TODO() Context { return todo }
具體結構如下,我們大致看下相關結構體中包含的字段,具體字段的含義及作用將在下面分析中會提及。
emptyCtx
type emptyCtx int // 空context
cancelCtx
type cancelCtx struct { Context // 父context mu sync.Mutex // protects following fields done atomic.Value // of chan struct{}, created lazily, closed by first cancel call children map[canceler]struct{} // set to nil by the first cancel call err error // cancel的原因 }
timerCtx
type timerCtx struct { cancelCtx //父context timer *time.Timer // 定時器 deadline time.Time // 截止時間 }
valueCtx
type valueCtx struct { Context // 父context key, val any // kv鍵值對 }
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 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 any) any { return nil } func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
cancelCtx
的實現相對復雜點,比如下面要介紹的timeCtx 底層也依賴它,所以弄懂cancelCtx
的工作原理就能很好的理解context
.
cancelCtx
不僅實現了Context
接口也實現了canceler
接口
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { if parent == nil { // 參數校驗 panic("cannot create context from nil parent") } // cancelCtx 初始化 c := newCancelCtx(parent) propagateCancel(parent, &c) // cancelCtx 父子關系維護及傳播取消信號 return &c, func() { c.cancel(true, Canceled) } // 返回cancelCtx對象及cannel方法 } // newCancelCtx returns an initialized cancelCtx. func newCancelCtx(parent Context) cancelCtx { return cancelCtx{Context: parent} }
用戶調用WithCancel
方法,傳入一個父 Context(這通常是一個 background
,作為根節點),返回新建的 context,并通過閉包的形式返回了一個 cancel 方法。如果想要取消context時需手動調用cancel方法。
cancelCtx對象初始化, 其結構如下:
type cancelCtx struct { // 父 context Context // parent context // 鎖 并發場景下保護cancelCtx結構中字段屬性的設置 mu sync.Mutex // protects following fields // done里存儲的是信號通道,其創建方式采用的是懶加載的方式 done atomic.Value // of chan struct{}, created lazily, closed by first cancel call // 記錄與父子cancelCtx對象, children map[canceler]struct{} // set to nil by the first cancel call // 記錄ctx被取消的原因 err error // set to non-nil by the first cancel call }
propagateCancel
// propagateCancel arranges for child to be canceled when parent is. func propagateCancel(parent Context, child canceler) { done := parent.Done() // 獲取parent ctx的信號通道 done if done == nil { // nil 代表 parent ctx 不是canelctx 類型,不會被取消,直接返回 return // parent is never canceled } select { // parent ctx 是cancelCtx類型,判斷其是否被取消 case <-done: // parent is already canceled child.cancel(false, parent.Err()) return default: } //parentCancelCtx往樹的根節點方向找到最近的context是cancelCtx類型的 if p, ok := parentCancelCtx(parent); ok { // 查詢到 p.mu.Lock() // 加鎖 if p.err != nil { // 祖父 ctx 已經被取消了,則 子cancelCtx 也需要調用cancel 方法來取消 // parent has already been canceled child.cancel(false, p.err) } else { // 使用map結構來維護 將child加入到祖父context中 if p.children == nil { p.children = make(map[canceler]struct{}) } p.children[child] = struct{}{} } p.mu.Unlock()// 解鎖 } else { // 開啟協程監聽 parent Ctx的取消信號 來通知child ctx 取消 atomic.AddInt32(&goroutines, +1) go func() { select { case <-parent.Done(): child.cancel(false, parent.Err()) case <-child.Done(): } }() } }
// parentCancelCtx returns the underlying *cancelCtx for parent. // It does this by looking up parent.Value(&cancelCtxKey) to find // the innermost enclosing *cancelCtx and then checking whether // parent.Done() matches that *cancelCtx. (If not, the *cancelCtx // has been wrapped in a custom implementation providing a // different done channel, in which case we should not bypass it.) // parentCancelCtx往樹的根節點方向找到最近的context是cancelCtx類型的 func parentCancelCtx(parent Context) (*cancelCtx, bool) { done := parent.Done() // closedchan 代表此時cancelCtx 已取消, nil 代表 ctx不是cancelCtx 類型的且不會被取消 if done == closedchan || done == nil { return nil, false } // 向上遍歷查詢canelCtx 類型的ctx p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) if !ok { // 沒有 return nil, false } // 存在判斷信號通道是不是相同 pdone, _ := p.done.Load().(chan struct{}) if pdone != done { return nil, false } return p, true }
cancelCtx
也實現了canceler
接口,實現可以 取消上下文的功能。
canceler
接口定義如下:
// A canceler is a context type that can be canceled directly. The // implementations are *cancelCtx and *timerCtx. type canceler interface { cancel(removeFromParent bool, err error) // 取消 Done() <-chan struct{} // 只讀通道,簡稱取消信號通道 }
cancelCtx
接口實現如下:
整體邏輯不復雜,邏輯簡化如下:
當前 cancelCtx 取消 且 與之其關聯的子 cancelCtx 也取消
根據removeFromParent標識來判斷是否將子 cancelCtx 移除
注意
由于信號通道的初始化采用的懶加載方式,所以有未初始化的情況;
已初始化的:調用close 函數關閉channel
未初始化的:用 closedchan
初始化,其closedchan
是已經關閉的channel。
// cancel closes c.done, cancels each of c's children, and, if // removeFromParent is true, removes c from its parent's children. func (c *cancelCtx) cancel(removeFromParent bool, err error) { if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // already canceled } c.err = err d, _ := c.done.Load().(chan struct{}) if d == nil { c.done.Store(closedchan) } else { close(d) } 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() if removeFromParent { removeChild(c.Context, c) } }
// removeChild removes a context from its parent. 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() }
closedchan
可重用的關閉通道,該channel通道默認已關閉
// closedchan is a reusable closed channel. var closedchan = make(chan struct{}) func init() { close(closedchan) // 調用close 方法關閉 }
cancelCtx
源碼已經分析完畢,那timerCtx
理解起來就很容易。
關注點:timerCtx
是如何取消上下文的,以及取消上下文的方式
WithTimeout
底層調用是WithDeadline 方法 ,截止時間是 now+timeout;
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
WithDeadline
整體邏輯并不復雜,從源碼中可分析出timerCtx
取消上下文 采用兩種方式 自動和手動;其中自動方式采用定時器去處理,到達觸發時刻,自動調用cancel方法。
deadline
: 截止時間
timer *time.Timer
: 定時器
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { if parent == nil { panic("cannot create context from nil parent") } 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(false, 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) } }
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to // implement Done and Err. It implements cancel by stopping its timer then // delegating to cancelCtx.cancel. type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time }
調用cancelCtx的cancel 方法
根據removeFromParent標識,為true 調用removeChild 方法 從它的父cancelCtx的children中移除
關閉定時器 ,防止內存泄漏(著重點)
func (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() }
valueCtx
結構體中有key
和val
兩個字段,WithValue
方法也是將數據存放在該字段上
func WithValue(parent Context, key, val any) Context { if parent == nil { panic("cannot create context from nil parent") } if key == nil { panic("nil key") } if !reflectlite.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{parent, key, val} } // A valueCtx carries a key-value pair. It implements Value for that key and // delegates all other calls to the embedded Context. type valueCtx struct { Context key, val any }
func (c *valueCtx) Value(key any) any { if c.key == key { // 判斷當前valuectx對象中的key是否匹配 return c.val } return value(c.Context, key) } // value() 向根部方向遍歷,直到找到與key對應的值 func value(c Context, key any) any { for { switch ctx := c.(type) { case *valueCtx: if key == ctx.key { return ctx.val } c = ctx.Context case *cancelCtx: if key == &cancelCtxKey { // 獲取cancelCtx對象 return c } c = ctx.Context case *timerCtx: if key == &cancelCtxKey { return &ctx.cancelCtx } c = ctx.Context case *emptyCtx: return nil default: return c.Value(key) } } }
以上就是關于“Go中的Context怎么使用”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。