最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go中Context使用源碼解析

 更新時(shí)間:2023年04月16日 10:49:11   作者:程序員wall  
這篇文章主要為大家介紹了Go中Context使用源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

本篇內(nèi)容的主題是Go中Context,想必已學(xué)習(xí)Go語言的大家在熟悉不過了。工作中我們也常會(huì)用到,但有時(shí)很少去注意它。

本打算將相關(guān)知識點(diǎn)歸總一下,發(fā)現(xiàn)其源碼不多,就打算對其源碼進(jìn)行分析一下。

context包是在go1.17是引入到標(biāo)準(zhǔn)庫中,且標(biāo)準(zhǔn)庫中大部分接口都將context.Context作為第一個(gè)參數(shù)。

context中文譯為“上下文”,實(shí)際代表的是goroutine的上下文。且多用于超時(shí)控制和多個(gè)goroutine間的數(shù)據(jù)傳遞。

本篇文章將帶領(lǐng)大家深入了解其內(nèi)部的工作原理。

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(): 返回的第一個(gè)值是 截止時(shí)間,到了這個(gè)時(shí)間點(diǎn),Context 會(huì)自動(dòng)觸發(fā) Cancel 動(dòng)作。返回的第二個(gè)值是 一個(gè)布爾值,true 表示設(shè)置了截止時(shí)間,false 表示沒有設(shè)置截止時(shí)間,如果沒有設(shè)置截止時(shí)間,就要手動(dòng)調(diào)用 cancel 函數(shù)取消 Context。

Done(): 返回一個(gè)只讀的通道(只有在被cancel后才會(huì)返回),類型為 struct{}。當(dāng)這個(gè)通道可讀時(shí),意味著parent context已經(jīng)發(fā)起了取消請求,根據(jù)這個(gè)信號,開發(fā)者就可以做一些清理動(dòng)作,退出goroutine。這里就簡稱信號通道吧!

Err():返回Context 被取消的原因

Value: 從Context中獲取與Key關(guān)聯(lián)的值,如果沒有就返回nil

2、Context的派生

2.1、創(chuàng)建Context對象

context包提供了四種方法來創(chuàng)建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:創(chuàng)建可以取消的Context
  • WithDeadline: 創(chuàng)建帶有截止時(shí)間的Context
  • WithTimeout:創(chuàng)建帶有超時(shí)時(shí)間的Context,底層調(diào)用的是WithDeadline方法
  • WithValue:創(chuàng)建可以攜帶KV型數(shù)據(jù)的Context

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

2.2、parent Context

context包默認(rèn)提供了兩個(gè)根context 對象backgroundtodo;看實(shí)現(xiàn)兩者都是由emptyCtx創(chuàng)建的,兩者的區(qū)別主要在語義上,

  • context.Background 是上下文的默認(rèn)值,所有其他的上下文都應(yīng)該從它衍生出來;
  • context.TODO 應(yīng)該僅在不確定應(yīng)該使用哪種上下文時(shí)使用
var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)
// Background 創(chuàng)建background context
func Background() Context {
	return background
}
// TODO 創(chuàng)建todo context
func TODO() Context {
	return todo
}

3、context 接口四種實(shí)現(xiàn)

具體結(jié)構(gòu)如下,我們大致看下相關(guān)結(jié)構(gòu)體中包含的字段,具體字段的含義及作用將在下面分析中會(huì)提及。

  • 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 // 定時(shí)器
	deadline time.Time // 截止時(shí)間
}
  • valueCtx
type valueCtx struct {
	Context // 父context
  key, val any // kv鍵值對
}

4、 emptyCtx 源碼分析

emptyCtx實(shí)現(xiàn)非常簡單,具體代碼如下,我們簡單看看就可以了

// 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 的實(shí)現(xiàn)相對復(fù)雜點(diǎn),比如下面要介紹的timeCtx 底層也依賴它,所以弄懂cancelCtx的工作原理就能很好的理解context.

cancelCtx 不僅實(shí)現(xiàn)了Context接口也實(shí)現(xiàn)了canceler接口

5.1、對象創(chuàng)建withCancel()

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	if parent == nil { // 參數(shù)校驗(yàn)
		panic("cannot create context from nil parent")
	}
  // cancelCtx 初始化
	c := newCancelCtx(parent)
	propagateCancel(parent, &c) // cancelCtx 父子關(guān)系維護(hù)及傳播取消信號
	return &c, func() { c.cancel(true, Canceled) } // 返回cancelCtx對象及cannel方法
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

用戶調(diào)用WithCancel方法,傳入一個(gè)父 Context(這通常是一個(gè) background,作為根節(jié)點(diǎn)),返回新建的 context,并通過閉包的形式返回了一個(gè) cancel 方法。如果想要取消context時(shí)需手動(dòng)調(diào)用cancel方法。

5.1.1、newCancelCtx

cancelCtx對象初始化, 其結(jié)構(gòu)如下:

type cancelCtx struct {
  // 父 context
	Context //  parent context
	// 鎖 并發(fā)場景下保護(hù)cancelCtx結(jié)構(gòu)中字段屬性的設(shè)置
	mu       sync.Mutex            // protects following fields 
  // done里存儲的是信號通道,其創(chuàng)建方式采用的是懶加載的方式
	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 類型,不會(huì)被取消,直接返回
		return // parent is never canceled
	}
	select { // parent ctx 是cancelCtx類型,判斷其是否被取消
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err())
		return
	default:
	}
  //parentCancelCtx往樹的根節(jié)點(diǎn)方向找到最近的context是cancelCtx類型的
	if p, ok := parentCancelCtx(parent); ok { // 查詢到
		p.mu.Lock() // 加鎖
		if p.err != nil { // 祖父 ctx 已經(jīng)被取消了,則 子cancelCtx 也需要調(diào)用cancel 方法來取消
			// parent has already been canceled
			child.cancel(false, p.err)
		} else { // 使用map結(jié)構(gòu)來維護(hù) 將child加入到祖父context中
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()// 解鎖
	} else { // 開啟協(xié)程監(jiān)聽 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往樹的根節(jié)點(diǎn)方向找到最近的context是cancelCtx類型的
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	done := parent.Done()
  // closedchan 代表此時(shí)cancelCtx 已取消, nil 代表 ctx不是cancelCtx 類型的且不會(huì)被取消
	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也實(shí)現(xiàn)了canceler接口,實(shí)現(xiàn)可以 取消上下文的功能。

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 接口實(shí)現(xiàn)如下:

整體邏輯不復(fù)雜,邏輯簡化如下:

  • 當(dāng)前 cancelCtx 取消 且 與之其關(guān)聯(lián)的子 cancelCtx 也取消
  • 根據(jù)removeFromParent標(biāo)識來判斷是否將子 cancelCtx 移除

注意

由于信號通道的初始化采用的懶加載方式,所以有未初始化的情況;

已初始化的:調(diào)用close 函數(shù)關(guān)閉channel

未初始化的:用 closedchan 初始化,其closedchan是已經(jīng)關(guān)閉的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

可重用的關(guān)閉通道,該channel通道默認(rèn)已關(guān)閉

// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})
func init() {
	close(closedchan) // 調(diào)用close 方法關(guān)閉
}

6、timerCtx 源碼分析

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

關(guān)注點(diǎn)timerCtx是如何取消上下文的,以及取消上下文的方式

6.1、對象創(chuàng)建 WithDeadline和WithTimeout

WithTimeout 底層調(diào)用是WithDeadline 方法 ,截止時(shí)間是 now+timeout;

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

WithDeadline 整體邏輯并不復(fù)雜,從源碼中可分析出timerCtx取消上下文 采用兩種方式 自動(dòng)手動(dòng);其中自動(dòng)方式采用定時(shí)器去處理,到達(dá)觸發(fā)時(shí)刻,自動(dòng)調(diào)用cancel方法。

deadline: 截止時(shí)間

timer *time.Timer : 定時(shí)器

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

  • 調(diào)用cancelCtx的cancel 方法
  • 根據(jù)removeFromParent標(biāo)識,為true 調(diào)用removeChild 方法 從它的父cancelCtx的children中移除
  • 關(guān)閉定時(shí)器 ,防止內(nèi)存泄漏(著重點(diǎn))
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、對象創(chuàng)建WithValue

valueCtx 結(jié)構(gòu)體中有keyval兩個(gè)字段,WithValue 方法也是將數(shù)據(jù)存放在該字段上

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 { // 判斷當(dāng)前valuectx對象中的key是否匹配
		return c.val
	}
	return value(c.Context, key)
}
// value() 向根部方向遍歷,直到找到與key對應(yīng)的值
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)
		}
	}
}

總結(jié):從Context 中獲取對應(yīng)的值需要通過遍歷的方式來獲取,這里告誡我們嵌套太多的context反而對性能會(huì)有影響

8、規(guī)范&注意事項(xiàng)

  • 不要把context存在一個(gè)結(jié)構(gòu)體當(dāng)中,顯式地傳入函數(shù)。context變量需要作為第一個(gè)參數(shù)使用,一般命名為ctx;
  • 即使方法允許,也不要傳入一個(gè)nil的Context,如果你不確定你要用什么Context的時(shí)候傳一個(gè)context.TODO
  • 使用context的Value相關(guān)方法只應(yīng)該用于在程序和接口中傳遞的和請求相關(guān)的元數(shù)據(jù),不要用它來傳遞一些可選的參數(shù)
  • 同樣的Context可以用來傳遞到不同的goroutine中,Context在多個(gè)goroutine中是安全的

以上就是Go中Context使用源碼解析的詳細(xì)內(nèi)容,更多關(guān)于Go Context源碼解析的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go實(shí)現(xiàn)fping功能

    go實(shí)現(xiàn)fping功能

    這篇文章主要介紹了go實(shí)現(xiàn)fping功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • 超越傳統(tǒng):Go語言并發(fā)編程的新境界

    超越傳統(tǒng):Go語言并發(fā)編程的新境界

    Go語言是一種開源的編程語言,以其強(qiáng)大的并發(fā)編程能力而聞名,本文將介紹Go語言并發(fā)編程的新境界,探討如何利用Go語言的特性來實(shí)現(xiàn)高效的并發(fā)編程,需要的朋友可以參考下
    2023-10-10
  • GoFrame框架使用避坑指南和實(shí)踐干貨

    GoFrame框架使用避坑指南和實(shí)踐干貨

    這篇文章主要為大家介紹了GoFrame框架使用避坑指南和實(shí)踐干貨,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • go gin中間件關(guān)于 c.next()、c.abort()和return的使用小結(jié)

    go gin中間件關(guān)于 c.next()、c.abort()和return的使用小結(jié)

    中間件的執(zhí)行順序是按照注冊順序執(zhí)行的,中間件可以通過 c.abort() + retrurn 來中止當(dāng)前中間件,后續(xù)中間件和處理器的處理流程,?這篇文章給大家介紹go gin中間件關(guān)于 c.next()、c.abort()和return的使用小結(jié),感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • Go語言struct類型介紹

    Go語言struct類型介紹

    這篇文章主要介紹了Go語言struct類型介紹,本文講解了struct的2種聲明方式,struct的匿名字段等內(nèi)容,需要的朋友可以參考下
    2015-01-01
  • 在Go中動(dòng)態(tài)替換SQL查詢中的日期參數(shù)的完整步驟

    在Go中動(dòng)態(tài)替換SQL查詢中的日期參數(shù)的完整步驟

    在處理數(shù)據(jù)庫查詢時(shí),經(jīng)常需要根據(jù)不同的輸入條件動(dòng)態(tài)地構(gòu)造SQL語句,尤其是在涉及日期范圍的查詢中,能夠根據(jù)實(shí)際需求調(diào)整查詢的起始和結(jié)束日期顯得尤為重要,在本文中,我將介紹如何在Go語言中實(shí)現(xiàn)動(dòng)態(tài)替換SQL查詢中的日期參數(shù),需要的朋友可以參考下
    2024-11-11
  • Go設(shè)計(jì)模式之原型模式圖文詳解

    Go設(shè)計(jì)模式之原型模式圖文詳解

    原型模式是一種創(chuàng)建型設(shè)計(jì)模式, 使你能夠復(fù)制已有對象, 而又無需使代碼依賴它們所屬的類,本文將通過圖片和文字讓大家可以詳細(xì)的了解Go的原型模式,感興趣的通過跟著小編一起來看看吧
    2023-07-07
  • Go語言協(xié)程處理數(shù)據(jù)有哪些問題

    Go語言協(xié)程處理數(shù)據(jù)有哪些問題

    協(xié)程(coroutine)是Go語言中的輕量級線程實(shí)現(xiàn),由Go運(yùn)行時(shí)(runtime)管理。本文為大家詳細(xì)介紹了Go中的協(xié)程,協(xié)程不需要搶占式調(diào)度,可以有效提高線程的任務(wù)并發(fā)性,而避免多線程的缺點(diǎn)
    2023-02-02
  • Go語言自定義linter靜態(tài)檢查工具

    Go語言自定義linter靜態(tài)檢查工具

    這篇文章主要介紹了Go語言自定義linter靜態(tài)檢查工具,Go語言是一門編譯型語言,編譯器將高級語言翻譯成機(jī)器語言,會(huì)先對源代碼做詞法分析,詞法分析是將字符序列轉(zhuǎn)換為Token序列的過程,文章詳細(xì)介紹需要的小伙伴可以參考一下
    2022-05-05
  • 在golang中使用Sync.WaitGroup解決等待的問題

    在golang中使用Sync.WaitGroup解決等待的問題

    這篇文章主要介紹了在golang中使用Sync.WaitGroup解決等待的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04

最新評論

东乌珠穆沁旗| 南京市| 葫芦岛市| 图们市| 岳阳县| 彰化市| 广宁县| 德庆县| 怀柔区| 莱芜市| 肥东县| 固安县| 同心县| 上饶市| 扎鲁特旗| 沽源县| 苍山县| 德清县| 康乐县| 西平县| 房产| 安达市| 临湘市| 陈巴尔虎旗| 金沙县| 台安县| 烟台市| 西吉县| 阳原县| 扶余县| 巴南区| 维西| 祁东县| 东乌| 喀喇沁旗| 胶州市| 榕江县| 浙江省| 文水县| 清流县| 临海市|