中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go中的Context怎么使用

發布時間:2023-04-17 17:17:26 來源:億速云 閱讀:169 作者:iii 欄目:開發技術

這篇“Go中的Context怎么使用”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Go中的Context怎么使用”文章吧。

    1、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

    2、Context的派生

    2.1、創建Context對象

    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

    簡單的樹狀關系如下(實際可衍生很多中):

    Go中的Context怎么使用

    2.2、parent Context

    context包默認提供了兩個根context 對象backgroundtodo;看實現兩者都是由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
    }

    3、context 接口四種實現

    Go中的Context怎么使用

    具體結構如下,我們大致看下相關結構體中包含的字段,具體字段的含義及作用將在下面分析中會提及。

    • 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鍵值對
    }

    4、 emptyCtx 源碼分析

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

    5、 cancelCtx 源碼分析

    cancelCtx 的實現相對復雜點,比如下面要介紹的timeCtx 底層也依賴它,所以弄懂cancelCtx的工作原理就能很好的理解context.

    cancelCtx 不僅實現了Context接口也實現了canceler接口

    5.1、對象創建withCancel()

    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方法。

    5.1.1、newCancelCtx

    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
    }
    5.1.2、propagateCancel

    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
    }

    5.2 canceler

    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 方法關閉
    }

    6、timerCtx 源碼分析

    cancelCtx源碼已經分析完畢,那timerCtx理解起來就很容易。

    關注點timerCtx是如何取消上下文的,以及取消上下文的方式

    6.1、對象創建 WithDeadline和WithTimeout

    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
    }

    6.2 timerCtx的cancel

    • 調用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()
    }

    7、valueCtx 源碼分析

    7.1、對象創建WithValue

    valueCtx 結構體中有keyval兩個字段,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
    }

    7.2、獲取value值

    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 == &amp;cancelCtxKey { // 獲取cancelCtx對象
    				return c
    			}
    			c = ctx.Context
    		case *timerCtx:
    			if key == &amp;cancelCtxKey {
    				return &amp;ctx.cancelCtx
    			}
    			c = ctx.Context
    		case *emptyCtx:
    			return nil
    		default:
    			return c.Value(key)
    		}
    	}
    }

    以上就是關于“Go中的Context怎么使用”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    察隅县| 沅陵县| 龙江县| 博白县| 大关县| 富宁县| 二连浩特市| 岳西县| 祥云县| 桑植县| 垦利县| 伊川县| 法库县| 策勒县| 铜山县| 荥经县| 遵化市| 南京市| 建阳市| 汕尾市| 资阳市| 遵义市| 宜宾县| 德阳市| 宜阳县| 邵武市| 永吉县| 新乐市| 元阳县| 布拖县| 佛山市| 衡阳县| 原阳县| 离岛区| 兰州市| 山丹县| 邵东县| 日照市| 四子王旗| 旬阳县| 东乌珠穆沁旗|