亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

對Go語言中的context包源碼分析

 更新時(shí)間:2022年02月09日 09:41:27   作者:zwf193071  
這篇文章主要對Go語言中的context包源碼進(jìn)行分析,context包析是1.15,context包定義了一個(gè)Context類型過這個(gè)Context接口類型,?就可以跨api邊界/跨進(jìn)程傳遞一些值,下面我們就來對context包源碼進(jìn)行分析,需要的小伙伴可以參考一下

一、包說明分析

context包:這個(gè)包分析的是1.15

context包定義了一個(gè)Context類型(接口類型),通過這個(gè)Context接口類型, 就可以跨api邊界/跨進(jìn)程傳遞一些deadline/cancel信號/request-scoped值.

發(fā)給server的請求中需要包含Context,server需要接收Context. 在整個(gè)函數(shù)調(diào)用鏈中,Context都需要進(jìn)行傳播. 期間是可以選擇將Context替換為派生Context(由With-系列函數(shù)生成). 當(dāng)一個(gè)Context是canceled狀態(tài)時(shí),所有派生的Context都是canceled狀態(tài).

With-系列函數(shù)(不包含WithValue)會基于父Context來生成一個(gè)派生Context, 還有一個(gè)CancelFunc函數(shù),調(diào)用這個(gè)CancelFun函數(shù)可取消派生對象和 "派生對象的派生對象的...",并且會刪除父Context和派生Context的引用關(guān)系, 最后還會停止相關(guān)定時(shí)器.如果不調(diào)用CancelFunc,直到父Context被取消或 定時(shí)器觸發(fā),派生Context和"派生Context的派生Context..."才會被回收, 否則就是泄露leak. go vet工具可以檢測到泄露.

使用Context包的程序需要遵循以下以下規(guī)則,目的是保持跨包兼容, 已經(jīng)使用靜態(tài)分析工具來檢查context的傳播:

  • Context不要存儲在struct內(nèi),直接在每個(gè)函數(shù)中顯示使用,作為第一個(gè)參數(shù),名叫ctx
  • 即使函數(shù)允許,也不要傳遞nil Context,如果實(shí)在不去確定就傳context.TODO
  • 在跨進(jìn)程和跨api時(shí),要傳request-scoped數(shù)據(jù)時(shí)用context Value,不要傳函數(shù)的可選參數(shù)
  • 不同協(xié)程可以傳遞同一Context到函數(shù),多協(xié)程并發(fā)使用Context是安全的

二、包結(jié)構(gòu)分析

核心的是:

? ? 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)
? ? type CancelFunc
? ? type Context

從上可以看出,核心的是Context接口類型,圍繞這個(gè)類型出現(xiàn)了With-系列函數(shù), 針對派生Context,還有取消函數(shù)CancelFunc.

還有兩個(gè)暴露的變量:

Canceled

context取消時(shí)由Context.Err方法返回

DeadlineExceeded

context超過deadline時(shí)由Context.Err方法返回

三、Context接口類型分析

context也稱上下文.

 type Context interface {
? ? ? Deadline() (deadline time.Time, ok bool)
? ? ? Done() <-chan struct{}
? ? ? Err() error
? ? ? Value(key interface{}) interface{}
? ? }

先看說明:

跨api時(shí),Context可以攜帶一個(gè)deadline/一個(gè)取消信號/某些值. 并發(fā)安全.

方法集分析:

Deadline

  • 返回的是截至?xí)r間
  • 這個(gè)時(shí)間表示的是任務(wù)完成時(shí)間
  • 到這個(gè)時(shí)間點(diǎn),Context的狀態(tài)已經(jīng)是be canceled(完成狀態(tài))
  • ok為false表示沒有設(shè)置deadline
  • 連續(xù)調(diào)用,返回的結(jié)果是相同的

Done

  • 返回的只讀信道
  • 任務(wù)完成,信道會被關(guān)閉,Context狀態(tài)是be canceled
  • Conetext永遠(yuǎn)不be canceled,Done可能返回nil
  • 連續(xù)調(diào)用,返回的結(jié)果是相同的
  • 信道的關(guān)閉會異步發(fā)生,且會在取消函數(shù)CancelFunc執(zhí)行完之后發(fā)生
  • 使用方面,Done需要配合select使用
  • 更多使用Done的例子在這個(gè)博客

Err

  • Done還沒關(guān)閉(此處指Done返回的只讀信道),Err返回nil
  • Done關(guān)閉了,Err返回non-nil的error
  • Context是be canceled,Err返回Canceled(這是之前分析的一個(gè)變量)
  • 如果是超過了截至日期deadline,Err返回DeadlineExceeded
  • 如果Err返回non-nil的error,后續(xù)再次調(diào)用,返回的結(jié)果是相同的

Value

  • 參數(shù)和返回值都是interface{}類型(這種解耦方式值得學(xué)習(xí))
  • Value就是通過key找value,如果沒找到,返回nil
  • 連續(xù)調(diào)用,返回的結(jié)果是相同的
  • 上下文值,只適用于跨進(jìn)程/跨api的request-scoped數(shù)據(jù)
  • 不適用于代替函數(shù)可選項(xiàng)
  • 一個(gè)上下文中,一個(gè)key對應(yīng)一個(gè)value
  • 典型用法:申請一個(gè)全局變量來放key,在context.WithValue/Context.Value中使用
  • key應(yīng)該定義為非暴露類型,避免沖突
  • 定義key時(shí),應(yīng)該支持類型安全的訪問value(通過key)
  • key不應(yīng)該暴露
    • 表示應(yīng)該通過暴露函數(shù)來進(jìn)行隔離(具體可以查看源碼中的例子)

四、后續(xù)分析規(guī)劃

看完Context的接口定義后,還需要查看With-系列函數(shù)才能知道context的定位, 在With-系列中會涉及到Context的使用和內(nèi)部實(shí)現(xiàn),那就先看WithCancel.

withCancel:

  • CancelFunc
  • newCancelCtx
    • cancelCtx
  • canceler
  • propagateCancel
    • parentCancelCtx

以下是分析出的通過規(guī)則:很多包對外暴露的是接口類型和幾個(gè)針對此類型的常用函數(shù). 接口類型暴露意味可擴(kuò)展,但是想擴(kuò)展之后繼續(xù)使用常用函數(shù),那擴(kuò)展部分就不能 修改常用函數(shù)涉及的部分,當(dāng)然也可以通過額外的接口繼續(xù)解耦. 針對"暴露接口和常用函數(shù)"這種套路,實(shí)現(xiàn)時(shí)會存在一個(gè)非暴露的實(shí)現(xiàn)類型, 常用函數(shù)就是基于這個(gè)實(shí)現(xiàn)類型實(shí)現(xiàn)的.在context.go中的實(shí)現(xiàn)類型是emptyCtx. 如果同時(shí)需要擴(kuò)展接口和常用函數(shù),最好是重新寫一個(gè)新包.

下面的分析分成兩部分:基于實(shí)現(xiàn)類型到常用函數(shù);擴(kuò)展功能以及如何擴(kuò)展.

五、基于實(shí)現(xiàn)類型到常用函數(shù)

Context接口的實(shí)現(xiàn)類型是emptyCtx. 

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

可以看到emptyCtx除了實(shí)現(xiàn)了context.Context接口,還實(shí)現(xiàn)了context.stringer接口, 注意下面的String不是實(shí)現(xiàn)的fmt.Stringer接口,而是未暴露的context.stringer接口. 正如empty的名字,對Context接口的實(shí)現(xiàn)都是空的,后續(xù)需要針對emptyCtx做擴(kuò)展.

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

這里通過兩個(gè)暴露的函數(shù)創(chuàng)建兩個(gè)空的emptyCtx實(shí)例,后續(xù)會根據(jù)不同場景來擴(kuò)展. 在注釋中,background實(shí)例的使用場景是:main函數(shù)/初始化/測試/或者作為top-level 的Context(派生其他Context);todo實(shí)例的使用場景是:不確定時(shí)用todo. 到此emptyCtx的構(gòu)造就理順了,就是Background()/TODO()兩個(gè)函數(shù),之后是針對她們 的擴(kuò)展和Context派生.

Context派生是基于With-系列函數(shù)實(shí)現(xiàn)的,我們先看對emptyCtx的擴(kuò)展, 這些擴(kuò)展至少會覆蓋一部分函數(shù),讓空的上下文變成支持某些功能的上下文, 取消信號/截至日期/值,3種功能的任意組合.

從源碼中可以看出,除了emptyCtx,還有cancelCtx/myCtx/myDoneCtx/otherContext/ timeCtx/valueCtx,他們有個(gè)共同特點(diǎn):基于Context組合的新類型, 我們尋找的對emptyCtx的擴(kuò)展,就是在這些新類型的方法中.

小技巧:emptyCtx已經(jīng)實(shí)現(xiàn)了context.Context,如果要修改方法的實(shí)現(xiàn), 唯一的方法就是利用Go的內(nèi)嵌進(jìn)行方法的覆蓋.簡單點(diǎn)說就是內(nèi)嵌到struct, struct再定義同樣簽名的方法,如果不需要數(shù)據(jù),內(nèi)嵌到接口也是一樣的.

cancelCtx

支持取消信號的上下文

type cancelCtx struct {
? Context

? mu ? ? ? sync.Mutex
? done ? ? chan struct{}
? children map[canceler]struct{}
? err ? ? ?error
}

看下方法:

? var cancelCtxKey int
? ? func (c *cancelCtx) Value(key interface{}) interface{} {
? ? ? if key == &cancelCtxKey {
? ? ? ? return c
? ? ? }
? ? ? return c.Context.Value(key)
? ? }
? ? func (c *cancelCtx) Done() <-chan struct{} {
? ? ? c.mu.Lock()
? ? ? if c.done == nil {
? ? ? ? c.done = make(chan struct{})
? ? ? }
? ? ? d := c.done
? ? ? c.mu.Unlock()
? ? ? return d
? ? }
? ? func (c *cancelCtx) Err() error {
? ? ? c.mu.Lock()
? ? ? err := c.err
? ? ? c.mu.Unlock()
? ? ? return err
? ? }

cancelCtxKey默認(rèn)是0,Value()要么返回自己,要么調(diào)用上下文Context.Value(), 具體使用后面再分析;Done()返回cancelCtx.done;Err()返回cancelCtx.err;

? ? func contextName(c Context) string {
? ? ? if s, ok := c.(stringer); ok {
? ? ? ? return s.String()
? ? ? }
? ? ? return reflectlite.TypeOf(c).String()
? ? }
? ? func (c *cancelCtx) String() string {
? ? ? return contextName(c.Context) + ".WithCancel"
? ? }

internal/reflectlite.TypeOf是獲取接口動態(tài)類型的反射類型, 如果接口是nil就返回nil,此處是獲取Context的類型, 從上面的分析可知,頂層Context要么是background,要么是todo, cancelCtx實(shí)現(xiàn)的context.stringer要么是context.Background.WithCancel, 要么是context.TODO.WithCancel.這里說的只是頂層Context下的, 多層派生Context的結(jié)構(gòu)也是類似的.

值得注意的是String()不屬于Context接口的方法集,而是emptyCtx對 context.stringer接口的實(shí)現(xiàn),cancelCxt內(nèi)嵌的Context,所以不會覆蓋 emptyCtx對String()的實(shí)現(xiàn). 

 ? var closedchan = make(chan struct{})
? ? func init() {
? ? ? close(closedchan)
? ? }

? ? 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
? ? ? if c.done == nil {
? ? ? ? c.done = closedchan
? ? ? } else {
? ? ? ? close(c.done)
? ? ? }
? ? ? 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)
? ? ? }
? ? }

cancel(),具體的取消信令對應(yīng)的操作,err不能為nil,err會存到cancelCtx.err, 如果已經(jīng)存了,表示取消操作已經(jīng)執(zhí)行.關(guān)閉done信道,如果之前沒有調(diào)用Done() 來獲取done信道,就返回一個(gè)closedchan(這是要給已關(guān)閉信道,可重用的), 之后是調(diào)用children的cancel(),最后就是在Context樹上移除當(dāng)前派生Context.

? ? func parentCancelCtx(parent Context) (*cancelCtx, bool) {
? ? ? done := parent.Done()
? ? ? if done == closedchan || done == nil {
? ? ? ? return nil, false
? ? ? }
? ? ? p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
? ? ? if !ok {
? ? ? ? return nil, false
? ? ? }
? ? ? p.mu.Lock()
? ? ? ok = p.done == done
? ? ? p.mu.Unlock()
? ? ? if !ok {
? ? ? ? return nil, false
? ? ? }
? ? ? return p, true
? ? }
? ? 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()
? ? }

removeChild首先判斷父Context是不是cancelCtx類型, 再判斷done信道和當(dāng)前Context的done信道是不是一致的, (如果不一致,說明:done信道是diy實(shí)現(xiàn)的,就不能刪掉了).

到此,cancelCtx覆蓋了cancelCtx.Context的Done/Err/Value, 同時(shí)實(shí)現(xiàn)了自己的打印函數(shù)String(),還實(shí)現(xiàn)了cancel(). 也就是說cancelCtx還實(shí)現(xiàn)了接口canceler:

type canceler interface {
? cancel(removeFromParent bool, err error)
? Done() <-chan struct{}
}
// cancelCtx.children的定義如下:
// children map[canceler]struct{}

執(zhí)行取消信號對應(yīng)的操作時(shí),其中有一步就是執(zhí)行children的cancel(), children的key是canceler接口類型,所以有對cancel()的實(shí)現(xiàn). cancelCtx實(shí)現(xiàn)了canceler接口,那么在派生Context就可以嵌套很多層, 或派生很多個(gè)cancelCtx.

func newCancelCtx(parent Context) cancelCtx {
? return cancelCtx{Context: parent}
}

非暴露的構(gòu)造函數(shù).

回顧一下:cancelCtx添加了Context對取消信號的支持. 只要觸發(fā)了"取消信號",使用方只需要監(jiān)聽done信道即可.

myCtx myDoneCtx otherContext屬于測試,等分析測試的時(shí)候再細(xì)說.

timerCtx

前面說到了取消信號對應(yīng)的上下文cancelCtx,timerCtx就是基于取消信號上下擴(kuò)展的

type timerCtx struct {
? cancelCtx
? timer *time.Timer

? deadline time.Time
}

注釋說明:內(nèi)嵌cancelCtx是為了復(fù)用Done和Err,擴(kuò)展了一個(gè)定時(shí)器和一個(gè)截至?xí)r間, 在定時(shí)器觸發(fā)時(shí)觸發(fā)cancelCtx.cancel()即可.

? func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
? ? ? return c.deadline, true
? ? }
? ? func (c *timerCtx) String() string {
? ? ? return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
? ? ? ? c.deadline.String() + " [" +
? ? ? ? time.Until(c.deadline).String() + "])"
? ? }
? ? func (c *timerCtx) cancel(removeFromParent bool, err error) {
? ? ? c.cancelCtx.cancel(false, err)
? ? ? if removeFromParent {
? ? ? ? removeChild(c.cancelCtx.Context, c)
? ? ? }
? ? ? c.mu.Lock()
? ? ? if c.timer != nil {
? ? ? ? c.timer.Stop()
? ? ? ? c.timer = nil
? ? ? }
? ? ? c.mu.Unlock()
? ? }

timerCtx內(nèi)嵌了cancelCtx,說明timerCtx也實(shí)現(xiàn)了canceler接口, 從源碼中可以看出,cancel()是重新實(shí)現(xiàn)了,String/Deadline都重新實(shí)現(xiàn)了.

cancel()中額外添加了定時(shí)器的停止操作.

這里沒有deadline設(shè)置和定時(shí)器timer開啟的操作,會放在With-系列函數(shù)中.

回顧一下: Context的deadline是機(jī)會取消信號實(shí)現(xiàn)的.

valueCtx

valueCtx和timerCtx不同,是直接基于Context的.

type valueCtx struct {
? Context
? key, val interface{}
}

一個(gè)valueCtx附加了一個(gè)kv對.實(shí)現(xiàn)了ValueString.

 ? func stringify(v interface{}) string {
? ? ? switch s := v.(type) {
? ? ? case stringer:
? ? ? ? return s.String()
? ? ? case string:
? ? ? ? return s
? ? ? }
? ? ? return "<not Stringer>"
? ? }
? ? func (c *valueCtx) String() string {
? ? ? return contextName(c.Context) + ".WithValue(type " +
? ? ? ? reflectlite.TypeOf(c.key).String() +
? ? ? ? ", val " + stringify(c.val) + ")"
? ? }
? ? func (c *valueCtx) Value(key interface{}) interface{} {
? ? ? if c.key == key {
? ? ? ? return c.val
? ? ? }
? ? ? return c.Context.Value(key)
? ? }

因?yàn)?code>valueCtx.val類型是接口類型interface{},所以獲取具體值時(shí), 使用了switch type.

六、With-系列函數(shù)

支持取消信號 WithCancel:

var Canceled = errors.New("context canceled")
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
? if parent == nil {
? ? panic("cannot create context from nil parent")
? }
? c := newCancelCtx(parent)
? propagateCancel(parent, &c)
? return &c, func() { c.cancel(true, Canceled) }
}

派生一個(gè)支持取消信號的Context,類型是cancelCtx,CancelFunc是取消操作, 具體是調(diào)用cancelCtx.cancel()函數(shù),err參數(shù)是Canceled.

? ? func propagateCancel(parent Context, child canceler) {
? ? ? done := parent.Done()
? ? ? if done == nil {
? ? ? ? return // parent is never canceled
? ? ? }

? ? ? select {
? ? ? case <-done:
? ? ? ? // parent is already canceled
? ? ? ? child.cancel(false, parent.Err())
? ? ? ? return
? ? ? default:
? ? ? }

? ? ? if p, ok := parentCancelCtx(parent); ok {
? ? ? ? p.mu.Lock()
? ? ? ? if p.err != nil {
? ? ? ? ? // parent has already been canceled
? ? ? ? ? child.cancel(false, p.err)
? ? ? ? } else {
? ? ? ? ? if p.children == nil {
? ? ? ? ? ? p.children = make(map[canceler]struct{})
? ? ? ? ? }
? ? ? ? ? p.children[child] = struct{}{}
? ? ? ? }
? ? ? ? p.mu.Unlock()
? ? ? } else {
? ? ? ? atomic.AddInt32(&goroutines, +1)
? ? ? ? go func() {
? ? ? ? ? select {
? ? ? ? ? case <-parent.Done():
? ? ? ? ? ? child.cancel(false, parent.Err())
? ? ? ? ? case <-child.Done():
? ? ? ? ? }
? ? ? ? }()
? ? ? }
? ? }

傳播取消信號.如果父Context不支持取消信號,那就不傳播. 如果父Context的取消信號已經(jīng)觸發(fā)(就是父Context的done信道已經(jīng)觸發(fā)或關(guān)閉), 之后判斷父Context是不是cancelCtx,如果是就將此Context丟到children中, 如果父Context不是cancelCtx,那就起協(xié)程監(jiān)聽父子Context的done信道.

小技巧:

select {
case <-done:
? child.cancel(false, parent.Err())
? return
default:
}

不加default,會等到done信道有動作;加了會立馬判斷done信道,done沒操作就結(jié)束select.

select {
case <-parent.Done():
? child.cancel(false, parent.Err())
case <-child.Done():
}

這個(gè)會等待,因?yàn)闆]有加default.

因?yàn)轫攲覥ontext目前只能是background和todo,不是cancelCtx, 所以頂層Context的直接派生Context不會觸發(fā)propagateCancel中的和children相關(guān)操作, 至少得3代及以后才有可能.

WithCancel的取消操作會釋放相關(guān)資源,所以在上下文操作完之后,最好盡快觸發(fā)取消操作. 觸發(fā)的方式是:done信道觸發(fā),要么有數(shù)據(jù),要么被關(guān)閉.

支持截至日期 WithDeadline:

 

 ? 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) }
? ? }

With-系列函數(shù)用于生成派生Context,函數(shù)內(nèi)部第一步都是判斷父Context是否為nil, WithDeadline第二步是判斷父Context是否支持deadline,支持就將取消信號傳遞給派生Context, 如果不支持,就為當(dāng)前派生Context支持deadline.

先理一下思路,目前Context的實(shí)現(xiàn)類型有4個(gè):emptyCtx/cancelCtx/timerCtx/valueCtx, 除了emptyCtx,實(shí)現(xiàn)Deadline()方法的只有timerCtx,(ps:這里的實(shí)現(xiàn)特指有業(yè)務(wù)意義的實(shí)現(xiàn)), 唯一可以構(gòu)造timerCtx的只有WithDeadline的第二步中. 這么說來,頂層Context不支持deadline,最多第二層派生支持deadline的Context, 第三層派生用于將取消信號進(jìn)行傳播.

WithCancel上面已經(jīng)分析了,派生一個(gè)支持取消信號的Context,并將父Context的取消信號 傳播到派生Context(ps:這么說有點(diǎn)繞,簡單點(diǎn)講就是將派生Context添加到父Context的children), 下面看看第一個(gè)構(gòu)造支持deadline的過程.

構(gòu)造timerCtx,傳播取消信號,判斷截至日期是否已過,如果沒過,利用time.AfterFunc創(chuàng)建定時(shí)器, 設(shè)置定時(shí)觸發(fā)的協(xié)程處理,之后返回派生Context和取消函數(shù).

可以看到,整個(gè)WithDeadline是基于WithCancel實(shí)現(xiàn)的,截至日期到期后,利用取消信號來做后續(xù)處理.

因?yàn)閠imerCtx是內(nèi)嵌了cancelCtx,所以有一個(gè)派生Context是可以同時(shí)支持取消和deadline的, 后面的value支持也是如此.

WithDeadline的注釋說明: 派生Context的deadline不晚于參數(shù),如果參數(shù)晚于父Context支持的deadline,使用父Context的deadline, 如果參數(shù)指定的比父Context早,或是父Context不支持deadline,那么派生Context會構(gòu)造一個(gè)新的timerCtx. 父Context的取消/派生Context的取消/或者deadline的過期,都會觸發(fā)取消信號對應(yīng)的操作執(zhí)行, 具體就是Done()信道會被關(guān)閉.

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

WitchTimeout是基于WithDeadline實(shí)現(xiàn)的,是一種擴(kuò)展,從設(shè)計(jì)上可以不加,但加了會增加調(diào)用者的便捷. WithTimeout可用在"慢操作"上.上下文使用完之后,應(yīng)該立即調(diào)用取消操作來釋放資源.

支持值WitchValue:

func WithValue(parent Context, key, val interface{}) 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}
}

只要是key能比較,就構(gòu)造一個(gè)valueCtx,用Value()獲取值時(shí),如果和當(dāng)前派生Context的key不匹配, 就會和父Context的key做匹配,如果不匹配,最后頂層Context會返回nil.

總結(jié)一下:如果是Value(),會一直通過派生Context找到頂層Context; 如果是deadline,會返回當(dāng)前派生Context的deadline,但會受到父Context的deadline和取消影響; 如果是取消函數(shù),會將傳播取消信號的相關(guān)Context都做取消操作. 最重要的是Context是一個(gè)樹形結(jié)構(gòu),可以組成很復(fù)雜的結(jié)構(gòu).

到目前為止,只了解了包的內(nèi)部實(shí)現(xiàn)(頂層Context的構(gòu)造/With-系列函數(shù)的派生), 具體使用,需要看例子和實(shí)際測試.

ps:一個(gè)包內(nèi)部如何復(fù)雜,對外暴露一定要簡潔.一個(gè)包是無法設(shè)計(jì)完美的,但是約束可以, 當(dāng)大家都接受一個(gè)包,并接受使用包的規(guī)則時(shí),這個(gè)包就成功了,context就是典型.

對于值,可以用WithValue派生,用Value取; 對于cancel/deadline,可以用WithDeadline/WithTimeout派生,通過Done信號獲取結(jié)束信號, 也可以手動用取消函數(shù)來觸發(fā)取消操作.整個(gè)包的功能就這么簡單.

七、擴(kuò)展功能以及如何擴(kuò)展

擴(kuò)展功能現(xiàn)在支持取消/deadline/value,擴(kuò)展這個(gè)層級不應(yīng)該放在這個(gè)包, 擴(kuò)展Context,也就是新建Context的實(shí)現(xiàn)類型,這個(gè)是可以的, 同樣實(shí)現(xiàn)類型需要承載擴(kuò)展功能,也不合適.

type canceler interface {
? cancel(removeFromParent bool, err error)
? Done() <-chan struct{}
}

接口canceler是保證取消信號可以在鏈上傳播,cancel方法由cancelCtx/timerCtx實(shí)現(xiàn), Done只由cancelCtx創(chuàng)建done信道,不管是從功能上還是方法上都沒有擴(kuò)展的必要.

剩下的就是Value擴(kuò)展成多kv對,這個(gè)主要還是要看應(yīng)用場景.

八、補(bǔ)充

Context被取消后Err返回Canceled錯(cuò)誤,超時(shí)之后Err返回DeadlineExceeded錯(cuò)誤, 這個(gè)DeadlineExceeded還有些說法: 

 var DeadlineExceeded error = deadlineExceededError{}

? type deadlineExceededError struct{}
? func (deadlineExceededError) Error() string {
? ? return "context deadline exceeded"
? }
? func (deadlineExceededError) Timeout() bool ? { return true }
? func (deadlineExceededError) Temporary() bool { return true }

再看看net.Error接口:

type Error interface {
? error
? Timeout() bool ? // Is the error a timeout?
? Temporary() bool // Is the error temporary?
}

context中的DeadlineExceeded默認(rèn)是實(shí)現(xiàn)了net.Error接口的實(shí)例. 這個(gè)是為后面走網(wǎng)絡(luò)超時(shí)留下的擴(kuò)展.

到此這篇關(guān)于對Go語言中的context包源碼分析的文章就介紹到這了,更多相關(guān)Go語言context包源碼分析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Golang中Channel的原理和使用技巧

    詳解Golang中Channel的原理和使用技巧

    Channel管道提供了一種機(jī)制,它在兩個(gè)并發(fā)執(zhí)行的協(xié)程之間進(jìn)行同步,并通過傳遞與該管道元素類型相符的值來進(jìn)行通信。本文主要介紹了Channel的原理和使用技巧,需要的可以參考一下
    2022-11-11
  • Gin 框架快速創(chuàng)建靜態(tài)文件下載Web服務(wù)

    Gin 框架快速創(chuàng)建靜態(tài)文件下載Web服務(wù)

    本文主要介紹了Gin 框架快速創(chuàng)建靜態(tài)文件下載Web服務(wù),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Go實(shí)現(xiàn)生產(chǎn)隨機(jī)密碼的示例代碼

    Go實(shí)現(xiàn)生產(chǎn)隨機(jī)密碼的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用Go實(shí)現(xiàn)生產(chǎn)隨機(jī)密碼的,文中的示例代碼簡潔易懂,具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下
    2023-09-09
  • Go?interface?接口的最佳實(shí)踐經(jīng)驗(yàn)分享

    Go?interface?接口的最佳實(shí)踐經(jīng)驗(yàn)分享

    go?的接口在go的編程里面用的十分頻繁,尤其是空接口的使用,因?yàn)橛辛私涌?,才使得Go語言變得異常的強(qiáng)大,今天給大家介紹下Go?interface?接口的最佳實(shí)踐經(jīng)驗(yàn)分享,感興趣的朋友一起看看吧
    2022-04-04
  • Go 1.21新增的slices包中切片函數(shù)用法詳解

    Go 1.21新增的slices包中切片函數(shù)用法詳解

    Go 1.21新增的 slices 包提供了很多和切片相關(guān)的函數(shù),可以用于任何類型的切片,本文通過代碼示例為大家介紹了部分切片函數(shù)的具體用法,感興趣的小伙伴可以了解一下
    2023-08-08
  • golang struct擴(kuò)展函數(shù)參數(shù)命名警告解決方法

    golang struct擴(kuò)展函數(shù)參數(shù)命名警告解決方法

    今天在使用VSCode編寫golang代碼時(shí),定義一個(gè)struct,擴(kuò)展幾個(gè)方法,需要的朋友可以參考下
    2017-02-02
  • Go語言中常見的坑以及高性能編程技巧分享

    Go語言中常見的坑以及高性能編程技巧分享

    代碼的穩(wěn)健性、高性能、可讀性是我們每一位coder必須去追求的目標(biāo),本文結(jié)合Go語言的特性做了相關(guān)總結(jié),感興趣的小伙伴可以了解一下
    2023-06-06
  • Golang的循環(huán)語句和循環(huán)控制語句詳解

    Golang的循環(huán)語句和循環(huán)控制語句詳解

    循環(huán)語句為了簡化程序中有規(guī)律的重復(fù)性操作,需要用到循環(huán)語句,和其他大多數(shù)編程語言一樣,GO的循環(huán)語句有for循環(huán),不同的是沒有while循環(huán),而循環(huán)控制語句可以改變循環(huán)語句的執(zhí)行過程,下面給大家介紹下go循環(huán)語句和循環(huán)控制語句的相關(guān)知識,一起看看吧
    2021-11-11
  • Go語言基礎(chǔ)知識總結(jié)(語法、變量、數(shù)值類型、表達(dá)式、控制結(jié)構(gòu)等)

    Go語言基礎(chǔ)知識總結(jié)(語法、變量、數(shù)值類型、表達(dá)式、控制結(jié)構(gòu)等)

    這篇文章主要介紹了Go語言基礎(chǔ)知識總結(jié)(語法、變量、數(shù)值類型、表達(dá)式、控制結(jié)構(gòu)等),本文匯總了Go語言的入門知識,需要的朋友可以參考下
    2014-10-10
  • 一文詳解Golang中的errors包

    一文詳解Golang中的errors包

    在 Golang 中,errors 包是用于處理錯(cuò)誤的標(biāo)準(zhǔn)庫, errors 包提供的功能比較簡單,使用起來非常方便,接下來就具體講解一下 errors 包提供的幾個(gè)函數(shù),感興趣的小伙伴跟著小編一起來看看吧
    2023-07-07

最新評論