Context-用來(lái)管理調(diào)用上下文,控制一個(gè)請(qǐng)求的生命周期。
直接看代碼:Context是一個(gè)接口
type Context interface {
//返回代表該Context過(guò)期的時(shí)間,和表示deadline是否被設(shè)置的bool值葱淳。
Deadline() (deadline time.Time, ok bool)
//返回一個(gè)channel漾唉,關(guān)閉該channel就代表關(guān)閉該Context怒竿。返回nil代表該Context不需要被關(guān)閉
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Context這個(gè)接口共有4個(gè)方法:
- Deadline方法是獲取設(shè)置的截止時(shí)間柜砾,第一個(gè)參數(shù)返回式是截時(shí)間轩拨,到了這個(gè)時(shí)間點(diǎn)践瓷,Context會(huì)自動(dòng)發(fā)起取消請(qǐng)求;第二個(gè)返回值ok==false時(shí)表示沒(méi)有設(shè)置截止時(shí)間亡蓉,如果需要取消的話晕翠,需要調(diào)用取消函數(shù)進(jìn)行取消。
- Done方法返回一個(gè)只讀的chan砍濒,類型為struct{}淋肾,我們?cè)趃oroutine中,如果該方法返回的chan可以讀取爸邢,則意味著parent context已經(jīng)發(fā)起了取消請(qǐng)求樊卓,我們通過(guò)Done方法收到這個(gè)信號(hào)后,就應(yīng)該做清理操作杠河,然后退出goroutine碌尔,釋放資源赶掖。
- Err方法返回取消的錯(cuò)誤原因,因?yàn)槭裁碈ontext被取消七扰。
- Value方法獲取該Context上綁定的值,是一個(gè)鍵值對(duì)陪白,所以要通過(guò)一個(gè)Key才可以獲取對(duì)應(yīng)的值颈走,這個(gè)值一般是線程安全的。
以上四個(gè)方法中常用的就是Done咱士。如果Context取消的時(shí)候立由,我們就可以得到一個(gè)關(guān)閉的chan,關(guān)閉的chan是可以讀取的序厉,所以只要可以讀取的時(shí)候锐膜,就意味著收到Context取消的信號(hào)了,以下是這個(gè)方法的經(jīng)典用法弛房。
func Stream(ctx context.Context, out chan<- Value) error {
for {
v, err := DoSomething(ctx)
if err != nil {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case out <- v:
}
}
}
Context接口并不需要我們實(shí)現(xiàn)道盏,Go內(nèi)置已經(jīng)幫我們實(shí)現(xiàn)了2個(gè),我們代碼中最開(kāi)始都是以這兩個(gè)內(nèi)置的作為最頂層的partent context文捶,衍生出更多的子Context荷逞。這些 Context 對(duì)象形成一棵樹(shù):當(dāng)一個(gè) Context 對(duì)象被取消時(shí),繼承自它的所有 Context 都會(huì)被取消粹排。兩個(gè)實(shí)現(xiàn)如下:
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
- Background:主要用于main函數(shù)种远、初始化以及測(cè)試代碼中,作為Context這個(gè)樹(shù)結(jié)構(gòu)的最頂層的Context顽耳,也就是根Context坠敷。
- TODO:它目前還不知道具體的使用場(chǎng)景,如果我們不知道該使用什么Context的時(shí)候射富,可以使用這個(gè)膝迎。
上面Background和TODO方法:都是emptyCtx結(jié)構(gòu)體類型,是一個(gè)不可取消胰耗。沒(méi)有設(shè)置截止時(shí)間弄抬,沒(méi)有攜帶任何值的Context。
// emptyCtx 不需要關(guān)閉宪郊,沒(méi)有任何鍵值對(duì)掂恕,也沒(méi)有過(guò)期時(shí)間。
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
}
emptyCtx實(shí)現(xiàn)Context接口的方法:可以看到這些方法什么都沒(méi)做弛槐,返回的都是nil或者零值懊亡。
Context的繼承衍生
有了如上的根Context,那么是如何衍生更多的子Context的呢乎串?這就要靠context包為我們提供的With系列的函數(shù)了店枣。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
- 這四個(gè)With函數(shù):接收的都有一個(gè)partent參數(shù),就是父Context。我們要基于這個(gè)父Context創(chuàng)建出子Context的意思鸯两,這種方式可以理解為子Context對(duì)父Context的繼承闷旧,也可以理解為基于父Context的衍生。
- 通過(guò)這些函數(shù)钧唐,就創(chuàng)建了一顆Context樹(shù)忙灼,樹(shù)的每個(gè)節(jié)點(diǎn)都可以有任意多個(gè)子節(jié)點(diǎn),節(jié)點(diǎn)層級(jí)可以有任意多個(gè)钝侠。
- WithCancel函數(shù):傳遞一個(gè)父Context作為參數(shù)该园,返回子Context以及一個(gè)取消函數(shù)用來(lái)取消Context。
- WithDeadline函數(shù):和WithCancel差不多帅韧,它會(huì)多傳遞一個(gè)截止時(shí)間參數(shù)里初,意味著到了這個(gè)時(shí)間點(diǎn),會(huì)自動(dòng)取消Context忽舟,當(dāng)然我們也可以不等到這個(gè)時(shí)候双妨,可以提前通過(guò)取消函數(shù)進(jìn)行取消。
- WithTimeout和WithDeadline基本上一樣叮阅,這個(gè)表示是超時(shí)自動(dòng)取消斥难,是多少時(shí)間后自動(dòng)取消Context的意思。
- WithValue函數(shù)和取消Context無(wú)關(guān)帘饶,它是為了生成一個(gè)綁定了一個(gè)鍵值對(duì)數(shù)據(jù)的Context哑诊,這個(gè)綁定的數(shù)據(jù)可以通過(guò)Context.Value方法訪問(wèn)到,后面我們會(huì)專門講及刻。
大家可能留意到镀裤,前三個(gè)函數(shù)都返回一個(gè)取消函數(shù)CancelFunc,這是一個(gè)函數(shù)類型缴饭。
type CancelFunc func()
這就是取消函數(shù)的類型:該函數(shù)可以取消一個(gè)Context暑劝,以及這個(gè)節(jié)點(diǎn)Context下所有的所有的Context,不管有多少層級(jí)颗搂。
WithValue傳遞元數(shù)據(jù)
通過(guò)Context我們也可以傳遞一些必須的元數(shù)據(jù)担猛,這些數(shù)據(jù)會(huì)附加在Context上以供使用。
var key string="name"
func main() {
ctx, cancel := context.WithCancel(context.Background())
//附加值
valueCtx:=context.WithValue(ctx,key,"【監(jiān)控1】")
go watch(valueCtx)
time.Sleep(10 * time.Second)
fmt.Println("可以了丢氢,通知監(jiān)控停止")
cancel()
//為了檢測(cè)監(jiān)控過(guò)是否停止傅联,如果沒(méi)有監(jiān)控輸出,就表示停止了
time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
for {
select {
case <-ctx.Done():
//取出值
fmt.Println(ctx.Value(key),"監(jiān)控退出疚察,停止了...")
return
default:
//取出值
fmt.Println(ctx.Value(key),"goroutine監(jiān)控中...")
time.Sleep(2 * time.Second)
}
}
}
- 在前面的例子蒸走,我們通過(guò)傳遞參數(shù)的方式,把name的值傳遞給監(jiān)控函數(shù)貌嫡。在這個(gè)例子里比驻,我們實(shí)現(xiàn)一樣的效果该溯,但是通過(guò)的是Context的Value的方式。
- 我們可以使用context.WithValue方法附加一對(duì)K-V的鍵值對(duì)别惦,這里Key必須是等價(jià)性的狈茉,也就是具有可比性;Value值要是線程安全的掸掸。
- 這樣我們就生成了一個(gè)新的Context氯庆,這個(gè)新的Context帶有這個(gè)鍵值對(duì),在使用的時(shí)候猾漫,可以通過(guò)Value方法讀取ctx.Value(key)。
- 記赘蟹铩:使用WithValue傳值悯周,一般是必須的值,不要什么值都傳遞陪竿。
Context 使用原則
- 不要把Context放在結(jié)構(gòu)體中禽翼,要以參數(shù)的方式傳遞
- 以Context作為參數(shù)的函數(shù)方法,應(yīng)該把Context作為第一個(gè)參數(shù)族跛,放在第一位闰挡。
- 給一個(gè)函數(shù)方法傳遞Context的時(shí)候,不要傳遞nil礁哄。如果不知道傳遞什么长酗,就使用context.TODO
- Context的Value相關(guān)方法應(yīng)該傳遞必須的數(shù)據(jù),不要什么數(shù)據(jù)都使用這個(gè)傳遞
- Context是線程安全的桐绒,可以放心的在多個(gè)goroutine中傳遞
Context總結(jié):
- 所有的context的父對(duì)象夺脾,也叫根對(duì)象。是一個(gè)空的context茉继,它不能被取消咧叭,它沒(méi)有值,從不會(huì)被取消烁竭,也沒(méi)有超時(shí)時(shí)間菲茬,它常常作為處理request的頂層context存在,然后通過(guò)WithCancel派撕、WithTimeout函數(shù)來(lái)創(chuàng)建子對(duì)象來(lái)獲得cancel(取消)婉弹、timeout(超時(shí))的能力
- 當(dāng)頂層的request請(qǐng)求函數(shù)結(jié)束后,我們就可以cancel掉某個(gè)context终吼,從而通知?jiǎng)e的routine結(jié)束
- WithValue方法可以把鍵值對(duì)加入context中马胧,讓不同的goroutine獲取。