欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語(yǔ)言學(xué)習(xí)之context包的用法詳解

 更新時(shí)間:2022年10月09日 14:17:58   作者:漫漫Coding路  
日常Go開發(fā)中,Context包是用的最多的一個(gè)了,幾乎所有函數(shù)的第一個(gè)參數(shù)都是ctx,那么我們?yōu)槭裁匆獋鬟fContext呢,Context又有哪些用法,底層實(shí)現(xiàn)是如何呢?相信你也一定會(huì)有探索的欲望,那么就跟著本篇文章,一起來(lái)學(xué)習(xí)吧

前言

日常 Go 開發(fā)中,Context 包是用的最多的一個(gè)了,幾乎所有函數(shù)的第一個(gè)參數(shù)都是 ctx,那么我們?yōu)槭裁匆獋鬟f Context 呢,Context 又有哪些用法,底層實(shí)現(xiàn)是如何呢?相信你也一定會(huì)有探索的欲望,那么就跟著本篇文章,一起來(lái)學(xué)習(xí)吧!

需求一

開發(fā)中肯定會(huì)調(diào)用別的函數(shù),比如 A 調(diào)用 B,在調(diào)用過(guò)程中經(jīng)常會(huì)設(shè)置超時(shí)時(shí)間,比如超過(guò)2s 就不等待 B 的結(jié)果了,直接返回,那么我們需要怎么做呢?

//?睡眠5s,模擬長(zhǎng)時(shí)間操作
func?FuncB()?(interface{},?error)?{
?time.Sleep(5?*?time.Second)
?return?struct{}{},?nil
}

func?FuncA()?(interface{},?error)?{

?var?res?interface{}
?var?err?error
?ch?:=?make(chan?interface{})

??//?調(diào)用FuncB(),將結(jié)果保存至?channel?中
?go?func()?{
??res,?err?=?FuncB()
??ch?<-?res
?}()

??//?設(shè)置一個(gè)2s的定時(shí)器
?timer?:=?time.NewTimer(2?*?time.Second)
??
??//?監(jiān)測(cè)是定時(shí)器先結(jié)束,還是?FuncB?先返回結(jié)果
?select?{
????
????//?超時(shí),返回默認(rèn)值
?case?<-timer.C:
??return?"default",?err
????
????//?FuncB?先返回結(jié)果,關(guān)閉定時(shí)器,返回?FuncB?的結(jié)果
?case?r?:=?<-ch:
??if?!timer.Stop()?{
???<-timer.C
??}
??return?r,?err
?}

}

func?main()?{
?res,?err?:=?FuncA()
?fmt.Println(res,?err)
}

上面我們的實(shí)現(xiàn),可以實(shí)現(xiàn)超過(guò)等待時(shí)間后,A 不等待 B,但是 B 并沒(méi)有感受到取消信號(hào),如果 B 是個(gè)計(jì)算密度型的函數(shù),我們也希望B 感知到取消信號(hào),及時(shí)取消計(jì)算并返回,減少資源浪費(fèi)。

另一種情況,如果存在多層調(diào)用,比如A 調(diào)用 B、C,B 調(diào)用 D、E,C調(diào)用 E、F,在超過(guò) A 的超時(shí)時(shí)間后,我們希望取消信號(hào)能夠一層層的傳遞下去,后續(xù)所有被調(diào)用到的函數(shù)都能感知到,及時(shí)返回。

需求二

在多層調(diào)用的時(shí)候,A->B->C->D,有些數(shù)據(jù)需要固定傳輸,比如 LogID,通過(guò)打印相同的 LogID,我們就能夠追溯某一次調(diào)用,方便問(wèn)題的排查。如果每次都需要傳參的話,未免太麻煩了,我們可以使用 Context 來(lái)保存。通過(guò)設(shè)置一個(gè)固定的 Key,打印日志時(shí)從中取出 value 作為 LogID。

const?LogKey?=?"LogKey"

//?模擬一個(gè)日志打印,每次從?Context?中取出?LogKey?對(duì)應(yīng)的?Value?作為L(zhǎng)ogID
type?Logger?struct{}
func?(logger?*Logger)?info(ctx?context.Context,?msg?string)?{
?logId,?ok?:=?ctx.Value(LogKey).(string)
?if?!ok?{
??logId?=?uuid.New().String()
?}
?fmt.Println(logId?+?"?"?+?msg)
}
var?logger?Logger

//?日志打印?并?調(diào)用?FuncB
func?FuncA(ctx?context.Context)?{
?logger.info(ctx,?"FuncA")
?FuncB(ctx)
}

func?FuncB(ctx?context.Context)?{
?logger.info(ctx,?"FuncB")
}

//?獲取初始化的,帶有?LogID?的?Context,一般在程序入口做
func?getLogCtx(ctx?context.Context)?context.Context?{
?logId,?ok?:=?ctx.Value(LogKey).(string)
?if?ok?{
??return?ctx
?}
?logId?=?uuid.NewString()
?return?context.WithValue(ctx,?LogKey,?logId)
}

func?main()?{
?ctx?=?getLogCtx(context.Background())
?FuncA(ctx)
}

這利用到了本篇文章講到的 valueCtx,繼續(xù)往下看,一起來(lái)學(xué)習(xí) valueCtx 是怎么實(shí)現(xiàn)的吧!

Context 接口

type?Context?interface?{

?Deadline()?(deadline?time.Time,?ok?bool)

?Done()?<-chan?struct{}

?Err()?error

?Value(key?interface{})?interface{}
}

Context 接口比較簡(jiǎn)單,定義了四個(gè)方法:

  • Deadline() 方法返回兩個(gè)值,deadline 表示 Context 將會(huì)在什么時(shí)間點(diǎn)取消,ok 表示是否設(shè)置了deadline。當(dāng) ok=false 時(shí),表示沒(méi)有設(shè)置deadline,那么此時(shí) deadline 將會(huì)是個(gè)零值。多次調(diào)用這個(gè)方法返回同樣的結(jié)果。
  • Done() 返回一個(gè)只讀的 channel,類型為 chan struct{},如果當(dāng)前的 Context 不支持取消,Done 返回 nil。我們知道,如果一個(gè) channel 中沒(méi)有數(shù)據(jù),讀取數(shù)據(jù)會(huì)阻塞;而如果channel被關(guān)閉,則可以讀取到數(shù)據(jù),因此可以監(jiān)聽 Done 返回的 channel,來(lái)獲取 Context 取消的信號(hào)。
  • Err() 返回 Done 返回的 channel 被關(guān)閉的原因。當(dāng) channel 未被關(guān)閉時(shí),Err() 返回 nil;channel 被關(guān)閉時(shí)則返回相應(yīng)的值,比如 Canceled 、DeadlineExceeded。Err() 返回一個(gè)非 nil 值之后,后面再次調(diào)用會(huì)返回相同的值。
  • Value() 返回 Context 保存的鍵值對(duì)中,key 對(duì)應(yīng)的 value,如果 key 不存在則返回 nil。

Done() 是一個(gè)比較常用的方法,下面是一個(gè)比較經(jīng)典的流式處理任務(wù)的示例:監(jiān)聽 ctx.Done() 是否被關(guān)閉來(lái)判斷任務(wù)是否需要取消,需要取消則返回相應(yīng)的原因;沒(méi)有取消則將計(jì)算的結(jié)果寫入到 out channel中。

?func?Stream(ctx?context.Context,?out?chan<-?Value)?error?{
??for?{
????
????//?處理數(shù)據(jù)
???v,?err?:=?DoSomething(ctx)
???if?err?!=?nil?{
????return?err
???}
????
????//?ctx.Done()?讀取到數(shù)據(jù),說(shuō)明獲取到了任務(wù)取消的信號(hào)
???select?{
???case?<-ctx.Done():
????return?ctx.Err()
????//?否則將結(jié)果輸出,繼續(xù)計(jì)算
???case?out?<-?v:
???}
??}
?}

Value() 也是一個(gè)比較常用的方法,用于在上下文中傳遞一些數(shù)據(jù)。使用 context.WithValue() 方法存入 key 和 value,通過(guò) Value() 方法則可以根據(jù) key 拿到 value。

func?main()?{
?ctx?:=?context.Background()
?c?:=?context.WithValue(ctx,?"key",?"value")
?v,?ok?:=?c.Value("key").(string)
?fmt.Println(v,?ok)
}

emptyCtx

Context 接口并不需要我們自己去手動(dòng)實(shí)現(xiàn),一般我們都是直接使用 context 包中提供的 Background() 方法和 TODO() 方法,來(lái)獲取最基礎(chǔ)的 Context。

var?(
?background?=?new(emptyCtx)
?todo???????=?new(emptyCtx)
)

func?Background()?Context?{
?return?background
}

func?TODO()?Context?{
?return?todo
}

Background() 方法一般用在 main 函數(shù),或者程序的初始化方法中;在我們不知道使用哪個(gè) Context,或者上文沒(méi)有傳遞 Context時(shí),可以使用 TODO()。

Background() 和 TODO() 都是基于 emptyCtx 生成的,從名字可以看出來(lái),emptyCtx 是一個(gè)空的Context,沒(méi)有 deadline、不能被取消、沒(méi)有鍵值對(duì)。

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

除了上面兩個(gè)最基本的 Context 外,context 包中提供了功能更加豐富的 Context,包括 valueCtx、cancelCtx、timerCtx,下面我們就挨個(gè)來(lái)看下。

valueCtx

使用示例

我們一般使用 context.WithValue() 方法向 Context 存入鍵值對(duì),然后通過(guò) Value() 方法根據(jù) key 得到 value,此種功能的實(shí)現(xiàn)就依賴 valueCtx。

func?main()?{
?ctx?:=?context.Background()
?c?:=?context.WithValue(ctx,?"myKey",?"myValue")

?v1?:=?c.Value("myKey")
?fmt.Println(v1.(string))

?v2?:=?c.Value("hello")
?fmt.Println(v2)?//??nil
}

類型定義

valueCtx 結(jié)構(gòu)體中嵌套了 Context,使用 key 、value 來(lái)保存鍵值對(duì):

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

WithValue

context包 對(duì)外暴露了 WithValue 方法,基于一個(gè) parent context 來(lái)創(chuàng)建一個(gè) valueCtx。從下面的源碼中可以看出,key 必須是可比較的!

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

*valueCtx 實(shí)現(xiàn)了 Value(),可以根據(jù) key 得到 value。這是一個(gè)向上遞歸尋找的過(guò)程,如果 key 不在當(dāng)前 valueCtx 中,會(huì)繼續(xù)向上找 parent Context,直到找到最頂層的 Context,一般最頂層的是 emptyCtx,而 emtpyCtx.Value() 返回 nil。

func?(c?*valueCtx)?Value(key?interface{})?interface{}?{
?if?c.key?==?key?{
??return?c.val
?}
?return?c.Context.Value(key)
}

cancelCtx

cancelCtx 是一個(gè)用于取消任務(wù)的 Context,任務(wù)通過(guò)監(jiān)聽 Context 是否被取消,來(lái)決定是否繼續(xù)處理任務(wù)還是直接返回。

如下示例中,我們?cè)?main 函數(shù)定義了一個(gè) cancelCtx,并在 2s 后調(diào)用 cancel() 取消 Context,即我們希望 doSomething() 在 2s 內(nèi)完成任務(wù),否則就可以直接返回,不需要再繼續(xù)計(jì)算浪費(fèi)資源了。

doSomething() 方法內(nèi)部,我們使用 select 監(jiān)聽任務(wù)是否完成,以及 Context 是否已經(jīng)取消,哪個(gè)先到就執(zhí)行哪個(gè)分支。方法模擬了一個(gè) 5s 的任務(wù),main 函數(shù)等待時(shí)間是2s,因此沒(méi)有完成任務(wù);如果main函數(shù)等待時(shí)間改為10s,則任務(wù)完成并會(huì)返回結(jié)果。

這只是一層調(diào)用,真實(shí)情況下可能會(huì)有多級(jí)調(diào)用,比如 doSomething 可能又會(huì)調(diào)用其他任務(wù),一旦 parent Context 取消,后續(xù)的所有任務(wù)都應(yīng)該取消。

func?doSomething(ctx?context.Context)?(interface{},?error)?{
?res?:=?make(chan?interface{})
?go?func()?{
??fmt.Println("do?something")
??time.Sleep(time.Second?*?5)
??res?<-?"done"
?}()

?select?{
?case?<-ctx.Done():
??return?nil,?ctx.Err()
?case?value?:=?<-res:
??return?value,?nil
?}
}

func?main()?{
?ctx,?cancel?:=?context.WithCancel(context.Background())
?go?func()?{
??time.Sleep(time.Second?*?2)
??cancel()
?}()
?res,?err?:=?doSomething(ctx)
?fmt.Println(res,?err)?//?nil?,?context?canceled
}

接下來(lái)就讓我們來(lái)研究下,cancelCtx 是如何實(shí)現(xiàn)取消的吧

類型定義

  • canceler 接口包含 cancel() 和 Done() 方法,*cancelCtx 和 *timerCtx 均實(shí)現(xiàn)了這個(gè)接口。
  • closedchan 是一個(gè)被關(guān)閉的channel,可以用于后面 Done() 返回
  • canceled 是一個(gè) err,用于 Context 被取消的原因
type?canceler?interface?{
?cancel(removeFromParent?bool,?err?error)
?Done()?<-chan?struct{}
}

//?closedchan?is?a?reusable?closed?channel.
var?closedchan?=?make(chan?struct{})

func?init()?{
?close(closedchan)
}

var?Canceled?=?errors.New("context?canceled")

CancelFunc 是一個(gè)函數(shù)類型定義,是一個(gè)取消函數(shù),有如下規(guī)范:

  • CancelFunc 告訴一個(gè)任務(wù)停止工作
  • CancelFunc 不會(huì)等待任務(wù)結(jié)束
  • CancelFunc 支持并發(fā)調(diào)用
  • 第一次調(diào)用后,后續(xù)的調(diào)用不會(huì)產(chǎn)生任何效果
type?CancelFunc?func()

&cancelCtxKey 是一個(gè)固定的key,用來(lái)返回 cancelCtx 自身

var?cancelCtxKey?int

cancelCtx

cancelCtx 是可以被取消的,它嵌套了 Context 接口,實(shí)現(xiàn)了 canceler 接口。cancelCtx 使用 children 字段保存同樣實(shí)現(xiàn) canceler 接口的子節(jié)點(diǎn),當(dāng) cancelCtx 被取消時(shí),所有的子節(jié)點(diǎn)也會(huì)取消。

type?cancelCtx?struct?{
?Context

?mu???????sync.Mutex????????????//?保護(hù)如下字段,保證線程安全
?done?????atomic.Value??????????//?保存?channel,懶加載,調(diào)用?cancel?方法時(shí)會(huì)關(guān)閉這個(gè)?channel
?children?map[canceler]struct{}?//?保存子節(jié)點(diǎn),第一次調(diào)用?cancel?方法時(shí)會(huì)置為?nil
?err??????error?????????????????//?保存為什么被取消,默認(rèn)為nil,第一次調(diào)用?cancel?會(huì)賦值
}

*cancelCtx 的 Value() 方法 和 *valueCtx 的 Value() 方法類似,只不過(guò)加了個(gè)固定的key: &cancelCtxKey。當(dāng)key 為 &cancelCtxKey 時(shí)返回自身

func?(c?*cancelCtx)?Value(key?interface{})?interface{}?{
?if?key?==?&cancelCtxKey?{
??return?c
?}
?return?c.Context.Value(key)
}

*cancelCtx 的 done 字段是懶加載的,只有在調(diào)用 Done() 方法 或者 cancel() 時(shí)才會(huì)賦值。

func?(c?*cancelCtx)?Done()?<-chan?struct{}?{
?d?:=?c.done.Load()
??
??//?如果已經(jīng)有值了,直接返回
?if?d?!=?nil?{
??return?d.(chan?struct{})
?}
??
??//?沒(méi)有值,加鎖賦值
?c.mu.Lock()
?defer?c.mu.Unlock()
?d?=?c.done.Load()
?if?d?==?nil?{
??d?=?make(chan?struct{})
??c.done.Store(d)
?}
?return?d.(chan?struct{})
}

Err 方法返回 cancelCtx 的 err 字段

func?(c?*cancelCtx)?Err()?error?{
???c.mu.Lock()
???err?:=?c.err
???c.mu.Unlock()
???return?err
}

WithCancel

那么我們?nèi)绾涡陆ㄒ粋€(gè) cancelCtx呢?context 包提供了 WithCancel() 方法,讓我們基于一個(gè) Context 來(lái)創(chuàng)建一個(gè) cancelCtx。WithCancel() 方法返回兩個(gè)字段,一個(gè)是基于傳入的 Context 生成的 cancelCtx,另一個(gè)是 CancelFunc。

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

WithCancel 調(diào)用了兩個(gè)外部方法:newCancelCtx 、propagateCancel。newCancelCtx 比較簡(jiǎn)單,根據(jù)傳入的 context,返回了一個(gè) cancelCtx 結(jié)構(gòu)體。

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

propagateCancel 從名字可以看出,就是將 cancel 傳播。如果父Context支持取消,那么我們需要建立一個(gè)通知機(jī)制,這樣父節(jié)點(diǎn)取消的時(shí)候,通知子節(jié)點(diǎn)也取消,層層傳播。

在 propagateCancel 中,如果 父Context 是 cancelCtx 類型且未取消,會(huì)將 子Context 掛在它下面,形成一個(gè)樹結(jié)構(gòu);其余情況都不會(huì)掛載。

func?propagateCancel(parent?Context,?child?canceler)?{
??
??//?如果?parent?不支持取消,那么就不支持取消傳播,直接返回
?done?:=?parent.Done()
?if?done?==?nil?{
??return?
?}

??//?到這里說(shuō)明?done?不為?nil,parent?支持取消
??
?select?{
?case?<-done:
??//?如果?parent?此時(shí)已經(jīng)取消了,那么直接告訴子節(jié)點(diǎn)也取消
??child.cancel(false,?parent.Err())
??return
?default:
?}

??//?到這里說(shuō)明此時(shí)?parent?還未取消
??
??//?如果?parent?是未取消的?cancelCtx?
?if?p,?ok?:=?parentCancelCtx(parent);?ok?{
????
????//?加鎖,防止并發(fā)更新
??p.mu.Lock()
????
????//?再次判斷,因?yàn)橛锌赡苌弦粋€(gè)獲得鎖的進(jìn)行了取消操作。
????//?如果?parent?已經(jīng)取消了,那么子節(jié)點(diǎn)也直接取消
??if?p.err?!=?nil?{
???child.cancel(false,?p.err)
??}?else?{
??????//?把子Context?掛到父節(jié)點(diǎn)?parent?cancelCtx?的?children字段下
??????//?之后?parent?cancelCtx?取消時(shí),能通知到所有的?子Context?
???if?p.children?==?nil?{
????p.children?=?make(map[canceler]struct{})
???}
???p.children[child]?=?struct{}{}
??}
??p.mu.Unlock()
?}?else?{
????
???//?parent?不是?cancelCtx?類型,可能是用戶自己實(shí)現(xiàn)的Context
??atomic.AddInt32(&goroutines,?+1)
????//?啟動(dòng)一個(gè)協(xié)程監(jiān)聽,如果?parent?取消了,子?Context?也取消
??go?func()?{
???select?{
???case?<-parent.Done():
????child.cancel(false,?parent.Err())
???case?<-child.Done():
???}
??}()
?}
}

cancel 方法就是來(lái)取消 cancelCtx,主要的工作是:關(guān)閉c.done 中的channel,給 err 賦值,然后級(jí)聯(lián)取消所有 子Context。如果 removeFromParent 為 true,會(huì)從父節(jié)點(diǎn)中刪除以該節(jié)點(diǎn)為樹頂?shù)臉洹?/p>

cancel() 方法只負(fù)責(zé)自己管轄的范圍,即自己以及自己的子節(jié)點(diǎn),然后根據(jù)配置判斷是否需要從父節(jié)點(diǎn)中移除自己為頂點(diǎn)的樹。如果子節(jié)點(diǎn)還有子節(jié)點(diǎn),那么由子節(jié)點(diǎn)負(fù)責(zé)處理,不用自己負(fù)責(zé)了。

propagateCancel() 中有三處調(diào)用了 cancel() 方法,傳入的 removeFromParent 都為 false,是因?yàn)楫?dāng)時(shí)根本沒(méi)有掛載,不需要移除。而 WithCancel 返回的 CancelFunc ,傳入的 removeFromParent 為 true,是因?yàn)檎{(diào)用 propagateCancel 有可能產(chǎn)生掛載,當(dāng)產(chǎn)生掛載時(shí),調(diào)用 cancel() 就需要移除了。

func?(c?*cancelCtx)?cancel(removeFromParent?bool,?err?error)?{
??
??//?err?是指取消的原因,必傳,cancelCtx?中是?errors.New("context?canceled")
?if?err?==?nil?{
??panic("context:?internal?error:?missing?cancel?error")
?}
??
??//?涉及到保護(hù)字段值的修改,都需要加鎖
?c.mu.Lock()
??
??//?如果該Context已經(jīng)取消過(guò)了,直接返回。多次調(diào)用cancel,不會(huì)產(chǎn)生額外效果
?if?c.err?!=?nil?{
??c.mu.Unlock()
??return?
?}
??
??//?給?err?賦值,這里?err?一定不為?nil
?c.err?=?err
??
??//?close?channel
?d,?_?:=?c.done.Load().(chan?struct{})
??//?因?yàn)閏.done?是懶加載,有可能存在?nil?的情況
??//?如果?c.done?中沒(méi)有值,直接賦值?closedchan;否則直接?close
?if?d?==?nil?{
??c.done.Store(closedchan)
?}?else?{
??close(d)
?}
??
??//?遍歷當(dāng)前?cancelCtx?所有的子Context,讓子節(jié)點(diǎn)也?cancel
??//?因?yàn)楫?dāng)前的Context?會(huì)主動(dòng)把子Context移除,子Context?不用主動(dòng)從parent中脫離
??//?因此?child.cancel?傳入的?removeFromParent?為false
?for?child?:=?range?c.children?{
??child.cancel(false,?err)
?}
??//?將?children?置空,相當(dāng)于移除自己的所有子Context
?c.children?=?nil
?c.mu.Unlock()
?
??//?如果當(dāng)前?cancelCtx?需要從上層的?cancelCtx移除,調(diào)用removeChild方法
??//?c.Context?就是自己的父Context
?if?removeFromParent?{
??removeChild(c.Context,?c)
?}
}

從propagateCancel方法中可以看到,只有parent 屬于 cancelCtx 類型 ,才會(huì)將自己掛載。因此 removeChild 會(huì)再次判斷 parent 是否為 cancelCtx,和之前的邏輯保持一致。找到的話,再將自己移除,需要注意的是,移除會(huì)把自己及其自己下面的所有子節(jié)點(diǎn)都移除。

如果上一步 propagateCancel 方法將自己掛載到了 A 上,但是在調(diào)用 cancel() 時(shí),A 已經(jīng)取消過(guò)了,此時(shí) parentCancelCtx() 會(huì)返回 false。不過(guò)這沒(méi)有關(guān)系,A 取消時(shí)已經(jīng)將掛載的子節(jié)點(diǎn)移除了,當(dāng)前的子節(jié)點(diǎn)不用將自己從 A 中移除了。

func?removeChild(parent?Context,?child?canceler)?{
??//?parent?是否為未取消的?cancelCtx
?p,?ok?:=?parentCancelCtx(parent)
?if?!ok?{
??return
?}
??//?獲取?parent?cancelCtx?的鎖,修改保護(hù)字段?children
?p.mu.Lock()
??//?將自己從?parent?cancelCtx?的?children?中刪除
?if?p.children?!=?nil?{
??delete(p.children,?child)
?}
?p.mu.Unlock()
}

parentCancelCtx 判斷 parent 是否為 未取消的 *cancelCtx。取消與否容易判斷,難判斷的是 parent 是否為  *cancelCtx,因?yàn)橛锌赡芷渌Y(jié)構(gòu)體內(nèi)嵌了 cancelCtx,比如 timerCtx,會(huì)通過(guò)比對(duì) channel 來(lái)確定。

func?parentCancelCtx(parent?Context)?(*cancelCtx,?bool)?{
??
??//?如果?parent?context?的?done?為?nil,?說(shuō)明不支持?cancel,那么就不可能是?cancelCtx
?//?如果?parent?context?的?done?為?closedchan,?說(shuō)明?parent?context?已經(jīng)?cancel?了
?done?:=?parent.Done()
?if?done?==?closedchan?||?done?==?nil?{
??return?nil,?false
?}
??
??//?到這里說(shuō)明支持取消,且沒(méi)有被取消
??
?//?如果?parent?context?屬于原生的?*cancelCtx?或衍生類型,需要繼續(xù)進(jìn)行后續(xù)判斷
?//?如果?parent?context?無(wú)法轉(zhuǎn)換到?*cancelCtx,則認(rèn)為非?cancelCtx,返回?nil,fasle
?p,?ok?:=?parent.Value(&cancelCtxKey).(*cancelCtx)
?if?!ok?{
??return?nil,?false
?}
?
??//?經(jīng)過(guò)上面的判斷后,說(shuō)明?parent?context?可以被轉(zhuǎn)換為?*cancelCtx,這時(shí)存在多種情況:
?//???-?parent?context?就是?*cancelCtx
?//???-?parent?context?是標(biāo)準(zhǔn)庫(kù)中的?timerCtx
?//???-?parent?context?是個(gè)自己自定義包裝的?cancelCtx
?//
?//?針對(duì)這?3?種情況需要進(jìn)行判斷,判斷方法就是:?
?//???判斷?parent?context?通過(guò)?Done()?方法獲取的?done?channel?與?Value?查找到的?context?的?done?channel?是否一致
?//?
?//?一致情況說(shuō)明?parent?context?為?cancelCtx?或?timerCtx?或?自定義的?cancelCtx?且未重寫?Done(),
?//?這種情況下可以認(rèn)為拿到了底層的?*cancelCtx
?//?
?//?不一致情況說(shuō)明?parent?context?是一個(gè)自定義的?cancelCtx?且重寫了?Done()?方法,并且并未返回標(biāo)準(zhǔn)?*cancelCtx?的
?//?的?done?channel,這種情況需要單獨(dú)處理,故返回?nil,?false
?pdone,?_?:=?p.done.Load().(chan?struct{})
?if?pdone?!=?done?{
??return?nil,?false
?}
?return?p,?true
}

timerCtx

簡(jiǎn)介

timerCtx 嵌入了 cancelCtx,并新增了一個(gè) timer 和 deadline 字段。timerCtx 的取消能力是復(fù)用 cancelCtx 的,只是在這個(gè)基礎(chǔ)上增加了定時(shí)取消而已。

在我們的使用過(guò)程中,有可能還沒(méi)到 deadline,任務(wù)就提前完成了,此時(shí)需要手動(dòng)調(diào)用 CancelFunc。

func?slowOperationWithTimeout(ctx?context.Context)?(Result,?error)?{
??ctx,?cancel?:=?context.WithTimeout(ctx,?100*time.Millisecond)
????defer?cancel()??//?如果未到截止時(shí)間,slowOperation就完成了,盡早調(diào)用?cancel()?釋放資源
??return?slowOperation(ctx)
}

類型定義

type?timerCtx?struct?{
???cancelCtx?//?內(nèi)嵌?cancelCtx
???timer?*time.Timer?//?受?cancelCtx.mu?互斥鎖的保護(hù)

???deadline?time.Time?//?截止時(shí)間
}

Deadline() 返回 deadline 字段的值

func?(c?*timerCtx)?Deadline()?(deadline?time.Time,?ok?bool)?{
?return?c.deadline,?true
}

WithDeadline

WithDeadline 基于parent Context 和 時(shí)間點(diǎn) d,返回了一個(gè)定時(shí)取消的 Context,以及一個(gè) CancelFunc。返回的Context 有三種情況被取消:1. 到達(dá)了指定時(shí)間,就會(huì)主動(dòng)取消;2. 手動(dòng)調(diào)用了 CancelFunc;3. 父Context取消,導(dǎo)致該Context被取消。這三種情況哪種先到,就會(huì)首次觸發(fā)取消操作,后續(xù)的再次取消不會(huì)產(chǎn)生任何效果。

如果傳入 parent Context 的 deadline 比指定的時(shí)間 d 還要早,此時(shí) d 就沒(méi)用處了,直接依賴 parent 取消傳播就可以了。

func?WithDeadline(parent?Context,?d?time.Time)?(Context,?CancelFunc)?{
??
??//?傳入的?parent?不能為?nil
?if?parent?==?nil?{
??panic("cannot?create?context?from?nil?parent")
?}
??
??//?parent?也有?deadline,并且比?d?還要早,直接依賴?parent?的取消傳播即可
?if?cur,?ok?:=?parent.Deadline();?ok?&&?cur.Before(d)?{
??//?The?current?deadline?is?already?sooner?than?the?new?one.
??return?WithCancel(parent)
?}
??
??//?定義?timerCtx?接口
?c?:=?&timerCtx{
??cancelCtx:?newCancelCtx(parent),
??deadline:??d,
?}
??
??//?設(shè)置傳播,如果parent?屬于?cancelCtx,會(huì)掛載到?children?字段上
?propagateCancel(parent,?c)
??
??//?距離截止時(shí)間?d?還有多久
?dur?:=?time.Until(d)
?if?dur?<=?0?{
????//?已經(jīng)到了截止時(shí)間,直接取消,同時(shí)從?parent?中取消掛載
????//?由于是超時(shí),取消時(shí)的?err?是?DeadlineExceeded
??c.cancel(true,?DeadlineExceeded)?
????
????//?再返回?c?和?CancelFunc,已經(jīng)取消掛載了,此時(shí)的?CancelFunc?不會(huì)從?parent?中取消掛載
????//?后面再次調(diào)用?CancelFunc?不會(huì)產(chǎn)生任何效果了
????//?主動(dòng)取消的話,err?是?Canceled
??return?c,?func()?{?c.cancel(false,?Canceled)?}
?}
??
??//?還沒(méi)有到截止時(shí)間,定義一個(gè)定時(shí)器,過(guò)了?dur?會(huì)自動(dòng)取消
?c.mu.Lock()
?defer?c.mu.Unlock()
?if?c.err?==?nil?{
??c.timer?=?time.AfterFunc(dur,?func()?{
??????//?由于是到了截止時(shí)間才取消,err?是?DeadlineExceeded
???c.cancel(true,?DeadlineExceeded)
??})
?}
??
??//?返回?c?和?cancelFunc,主動(dòng)取消的?err?是?Canceled
?return?c,?func()?{?c.cancel(true,?Canceled)?}
}

接下來(lái)我們看下 cancel 方法,timerCtx 的 cancel 方法 就是調(diào)用內(nèi)嵌 cancelCtx 的 cancel() 方法,默認(rèn)是不從父節(jié)點(diǎn)移除

func?(c?*timerCtx)?cancel(removeFromParent?bool,?err?error)?{
?c.cancelCtx.cancel(false,?err)
??
??//?從父節(jié)點(diǎn)中移除
?if?removeFromParent?{
??removeChild(c.cancelCtx.Context,?c)
?}
??
??//?把定時(shí)器停了,釋放資源
??//?有可能還沒(méi)到deadline,手動(dòng)觸發(fā)了?CancelFunc,此時(shí)把?timer?停了
?c.mu.Lock()
?if?c.timer?!=?nil?{
??c.timer.Stop()
??c.timer?=?nil
?}
?c.mu.Unlock()
}

WithTimeout

WithTimeout 就是基于 WithDeadline,deadline 就是基于當(dāng)前時(shí)間計(jì)算的

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

總結(jié)

本篇文章,我們通過(guò)源碼+示例的方式,一起學(xué)習(xí)了 context 包相關(guān)的結(jié)構(gòu)以及實(shí)現(xiàn)邏輯,包括如下內(nèi)容

Context 接口:定義了一些接口方法和規(guī)范

emptyCtx:空的Context,Background() 和 TODO() 方法就是使用的 emptyCtx

valueCtx:用于保存鍵值對(duì),查詢時(shí)是遞歸查詢,可以用于 LogID 這種全局 id 的保存

cancelCtx:可以取消的Context,用于取消信號(hào)的傳遞

timerCtx:定時(shí)取消的 cancelCtx

以上就是Go語(yǔ)言學(xué)習(xí)之context包的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Go語(yǔ)言 context包的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語(yǔ)言接口與多態(tài)詳細(xì)介紹

    Go語(yǔ)言接口與多態(tài)詳細(xì)介紹

    Go語(yǔ)言的接口類型是一組方法定義的集合,它體現(xiàn)了多態(tài)性、高內(nèi)聚和低耦合的設(shè)計(jì)思想,接口通過(guò)interface關(guān)鍵字定義,無(wú)需實(shí)現(xiàn)具體方法,任何實(shí)現(xiàn)了接口所有方法的類型即視為實(shí)現(xiàn)了該接口,感興趣的朋友一起看看吧
    2024-09-09
  • 模塊一 GO語(yǔ)言基礎(chǔ)知識(shí)-庫(kù)源碼文件

    模塊一 GO語(yǔ)言基礎(chǔ)知識(shí)-庫(kù)源碼文件

    這篇文章主要介紹了模塊一 GO語(yǔ)言基礎(chǔ)知識(shí)-庫(kù)源碼文件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Golang空接口與類型斷言的實(shí)現(xiàn)

    Golang空接口與類型斷言的實(shí)現(xiàn)

    本文主要介紹了Golang空接口與類型斷言的實(shí)現(xiàn),文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Go語(yǔ)言Gin處理響應(yīng)方式詳解

    Go語(yǔ)言Gin處理響應(yīng)方式詳解

    gin框架封裝了常用的數(shù)據(jù)格式方法響應(yīng)于客戶端,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言Gin處理響應(yīng)方式的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • Go?處理大數(shù)組使用?for?range?和?for?循環(huán)的區(qū)別

    Go?處理大數(shù)組使用?for?range?和?for?循環(huán)的區(qū)別

    這篇文章主要介紹了Go處理大數(shù)組使用for?range和for循環(huán)的區(qū)別,對(duì)于遍歷大數(shù)組而言,for循環(huán)能比f(wàn)or?range循環(huán)更高效與穩(wěn)定,這一點(diǎn)在數(shù)組元素為結(jié)構(gòu)體類型更加明顯,下文具體分析感興趣得小伙伴可以參考一下
    2022-05-05
  • uber go zap 日志框架支持異步日志輸出

    uber go zap 日志框架支持異步日志輸出

    這篇文章主要為大家介紹了uber go zap 日志框架支持異步日志輸出示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Go語(yǔ)言讀取文件的方法小結(jié)

    Go語(yǔ)言讀取文件的方法小結(jié)

    寫程序時(shí)經(jīng)常需要從一個(gè)文件讀取數(shù)據(jù),然后輸出到另一個(gè)文件,這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言讀取文件的幾種方法,希望對(duì)大家有所幫助
    2024-01-01
  • Go?Java算法之單詞搜索示例詳解

    Go?Java算法之單詞搜索示例詳解

    這篇文章主要為大家介紹了Go?Java算法之單詞搜索示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 一文詳解Golang的中間件設(shè)計(jì)模式

    一文詳解Golang的中間件設(shè)計(jì)模式

    最近在看一些rpc框架的使用原理和源碼的時(shí)候,對(duì)中間件的實(shí)現(xiàn)非常感興趣,所以這篇文章就來(lái)和大家聊聊Golang的中間件設(shè)計(jì)模式,希望對(duì)大家有所幫助
    2023-03-03
  • Go語(yǔ)言實(shí)現(xiàn)LRU算法的核心思想和實(shí)現(xiàn)過(guò)程

    Go語(yǔ)言實(shí)現(xiàn)LRU算法的核心思想和實(shí)現(xiàn)過(guò)程

    這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)LRU算法的核心思想和實(shí)現(xiàn)過(guò)程,LRU算法是一種常用的緩存淘汰策略,它的核心思想是如果一個(gè)數(shù)據(jù)在最近一段時(shí)間內(nèi)沒(méi)有被訪問(wèn)到,那么在將來(lái)它被訪問(wèn)的可能性也很小,因此可以將其淘汰,感興趣想要詳細(xì)了解可以參考下文
    2023-05-05

最新評(píng)論