深入理解Golang之context

為什么需要context

在并發(fā)程序中吧寺,由于超時(shí)论泛、取消操作或者一些異常情況骇钦,往往需要進(jìn)行搶占操作或者中斷后續(xù)操作辩撑。熟悉channel的應(yīng)該都見(jiàn)過(guò)使用done channel來(lái)處理此類(lèi)問(wèn)題界斜。比如以下這個(gè)例子:

func?main()?{????messages?:=?make(chan?int,?10)????

done?:=?make(chan?bool)????

defer?close(messages)???

?//?consumer????go?func()?{????????ticker?:=?time.NewTicker(1?*?time.Second)????????for?_?=?range?ticker.C?{????????????select?{????????????case?<-done:???????????????? ?

?fmt.Println("child?process?interrupt...")????????????????

return????????????

default:????????????????

fmt.Printf("send?message:?%d\n",?<-messages)????????????}????????}????}()??

??//?producer????for?i?:=?0;?i?<?10;?i++?{????????messages?<-?i????}???

?time.Sleep(5?*?time.Second)????

close(done)????time.Sleep(1?*?time.Second)????

fmt.Println("main?process?exit!")}


上述例子中定義了一個(gè)buffer為0的channel done, 子協(xié)程運(yùn)行著定時(shí)任務(wù)。如果主協(xié)程需要在某個(gè)時(shí)刻發(fā)送消息通知子協(xié)程中斷任務(wù)退出合冀,那么就可以讓子協(xié)程監(jiān)聽(tīng)這個(gè)done channel各薇,一旦主協(xié)程關(guān)閉done channel,那么子協(xié)程就可以推出了君躺,這樣就實(shí)現(xiàn)了主協(xié)程通知子協(xié)程的需求峭判。這很好,但是這也是有限的棕叫。

如果我們可以在簡(jiǎn)單的通知上附加傳遞額外的信息來(lái)控制取消:為什么取消林螃,或者有一個(gè)它必須要完成的最終期限,更或者有多個(gè)取消選項(xiàng)俺泣,我們需要根據(jù)額外的信息來(lái)判斷選擇執(zhí)行哪個(gè)取消選項(xiàng)疗认。

考慮下面這種情況:假如主協(xié)程中有多個(gè)任務(wù)1, 2, …m,主協(xié)程對(duì)這些任務(wù)有超時(shí)控制伏钠;而其中任務(wù)1又有多個(gè)子任務(wù)1, 2, …n侮邀,任務(wù)1對(duì)這些子任務(wù)也有自己的超時(shí)控制,那么這些子任務(wù)既要感知主協(xié)程的取消信號(hào)贝润,也需要感知任務(wù)1的取消信號(hào)绊茧。

如果還是使用done channel的用法,我們需要定義兩個(gè)done channel打掘,子任務(wù)們需要同時(shí)監(jiān)聽(tīng)這兩個(gè)done channel华畏。嗯,這樣其實(shí)好像也還行哈尊蚁。但是如果層級(jí)更深亡笑,如果這些子任務(wù)還有子任務(wù),那么使用done channel的方式將會(huì)變得非常繁瑣且混亂横朋。

我們需要一種優(yōu)雅的方案來(lái)實(shí)現(xiàn)這樣一種機(jī)制:

上層任務(wù)取消后仑乌,所有的下層任務(wù)都會(huì)被取消;

中間某一層的任務(wù)取消后,只會(huì)將當(dāng)前任務(wù)的下層任務(wù)取消晰甚,而不會(huì)影響上層的任務(wù)以及同級(jí)任務(wù)衙传。

這個(gè)時(shí)候context就派上用場(chǎng)了。我們首先看看context的結(jié)構(gòu)設(shè)計(jì)和實(shí)現(xiàn)原理厕九。

context是什么

context接口

先看Context接口結(jié)構(gòu)蓖捶,看起來(lái)非常簡(jiǎn)單。

type?Context?interface?{??

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

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

?Err()?error????

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


Context接口包含四個(gè)方法:

Deadline返回綁定當(dāng)前context的任務(wù)被取消的截止時(shí)間扁远;如果沒(méi)有設(shè)定期限俊鱼,將返回ok == false。

Done當(dāng)綁定當(dāng)前context的任務(wù)被取消時(shí)畅买,將返回一個(gè)關(guān)閉的channel并闲;如果當(dāng)前context不會(huì)被取消,將返回nil谷羞。

Err如果Done返回的channel沒(méi)有關(guān)閉帝火,將返回nil;如果Done返回的channel已經(jīng)關(guān)閉,將返回非空的值表示任務(wù)結(jié)束的原因洒宝。如果是context被取消购公,Err將返回Canceled;如果是context超時(shí)雁歌,Err將返回DeadlineExceeded宏浩。

Value返回context存儲(chǔ)的鍵值對(duì)中當(dāng)前key對(duì)應(yīng)的值,如果沒(méi)有對(duì)應(yīng)的key,則返回nil靠瞎。

可以看到Done方法返回的channel正是用來(lái)傳遞結(jié)束信號(hào)以搶占并中斷當(dāng)前任務(wù)比庄;Deadline方法指示一段時(shí)間后當(dāng)前goroutine是否會(huì)被取消;以及一個(gè)Err方法乏盐,來(lái)解釋goroutine被取消的原因佳窑;而Value則用于獲取特定于當(dāng)前任務(wù)樹(shù)的額外信息。而context所包含的額外信息鍵值對(duì)是如何存儲(chǔ)的呢父能?其實(shí)可以想象一顆樹(shù)神凑,樹(shù)的每個(gè)節(jié)點(diǎn)可能攜帶一組鍵值對(duì),如果當(dāng)前節(jié)點(diǎn)上無(wú)法找到key所對(duì)應(yīng)的值,就會(huì)向上去父節(jié)點(diǎn)里找何吝,直到根節(jié)點(diǎn)溉委,具體后面會(huì)說(shuō)到。

再來(lái)看看context包中的其他關(guān)鍵內(nèi)容爱榕。

emptyCtx

emptyCtx是一個(gè)int類(lèi)型的變量瓣喊,但實(shí)現(xiàn)了context的接口。emptyCtx沒(méi)有超時(shí)時(shí)間黔酥,不能取消藻三,也不能存儲(chǔ)任何額外信息洪橘,所以emptyCtx用來(lái)作為context樹(shù)的根節(jié)點(diǎ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?intfunc?(*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"}

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

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

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

一般不會(huì)直接使用emptyCtx棵帽,而是使用由emptyCtx實(shí)例化的兩個(gè)變量熄求,分別可以通過(guò)調(diào)用Background和TODO方法得到,但這兩個(gè)context在實(shí)現(xiàn)上是一樣的岖寞。那么Background和TODO方法得到的context有什么區(qū)別呢抡四?

Background和TODO只是用于不同場(chǎng)景下:

Background通常被用于主函數(shù)柜蜈、初始化以及測(cè)試中仗谆,作為一個(gè)頂層的context,也就是說(shuō)一般我們創(chuàng)建的context都是基于Background淑履;而TODO是在不確定使用什么context的時(shí)候才會(huì)使用隶垮。

下面將介紹兩種不同功能的基礎(chǔ)context類(lèi)型:valueCtx和cancelCtx。

valueCtx

valueCtx結(jié)構(gòu)體

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

func?(c?*valueCtx)?Value(key?interface{})?interface{}?{??

??if?c.key?==?key?{????????return?c.val????}???

?return?c.Context.Value(key)}

valueCtx利用一個(gè)Context類(lèi)型的變量來(lái)表示父節(jié)點(diǎn)context秘噪,所以當(dāng)前context繼承了父context的所有信息狸吞;valueCtx類(lèi)型還攜帶一組鍵值對(duì),也就是說(shuō)這種context可以攜帶額外的信息指煎。valueCtx實(shí)現(xiàn)了Value方法蹋偏,用以在context鏈路上獲取key對(duì)應(yīng)的值,如果當(dāng)前context上不存在需要的key,會(huì)沿著context鏈向上尋找key對(duì)應(yīng)的值至壤,直到根節(jié)點(diǎn)威始。

WithValue

WithValue用以向context添加鍵值對(duì):

func?WithValue(parent?Context,?key,?val?interface{})?Context?{??

??if?key?==?nil?{????????panic("nil?key")????}???

?if?!reflect.TypeOf(key).Comparable()?{????????panic("key?is?not?comparable")????}???

?return?&valueCtx{parent,?key,?val}}

這里添加鍵值對(duì)不是在原context結(jié)構(gòu)體上直接添加,而是以此context作為父節(jié)點(diǎn)像街,重新創(chuàng)建一個(gè)新的valueCtx子節(jié)點(diǎn)黎棠,將鍵值對(duì)添加在子節(jié)點(diǎn)上,由此形成一條context鏈镰绎。獲取value的過(guò)程就是在這條context鏈上由尾部上前搜尋:


cancelCtx

cancelCtx結(jié)構(gòu)體

type?cancelCtx?struct?{???

?Context????mu???????sync.Mutex????????????//?protects?following?fields????

?done?????chan?struct{}?????????//?created?lazily,?closed?by?first?cancel?call????

?children?map[canceler]struct{}?//?set?to?nil?by?the?first?cancel?call????

? err??????error?????????????????//?set?to?non-nil?by?the?first?cancel?call}

type?canceler?interface?{????cancel(removeFromParent?bool,?err?error)????

Done()?<-chan?struct{}

}

跟valueCtx類(lèi)似脓斩,cancelCtx中也有一個(gè)context變量作為父節(jié)點(diǎn);變量done表示一個(gè)channel畴栖,用來(lái)表示傳遞關(guān)閉信號(hào)随静;children表示一個(gè)map,存儲(chǔ)了當(dāng)前context節(jié)點(diǎn)下的子節(jié)點(diǎn)吗讶;err用于存儲(chǔ)錯(cuò)誤信息表示任務(wù)結(jié)束的原因燎猛。

再來(lái)看一下cancelCtx實(shí)現(xiàn)的方法:

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}

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

}????//?設(shè)置取消原因???

?c.err?=?err????設(shè)置一個(gè)關(guān)閉的channel或者將done?channel關(guān)閉,用以發(fā)送關(guān)閉信號(hào)???

?if?c.done?==?nil?{???????

?c.done?=?closedchan???

?}?else?{???

?????close(c.done)????}????

//?將子節(jié)點(diǎn)context依次取消????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?{????????//?將當(dāng)前context節(jié)點(diǎn)從父節(jié)點(diǎn)上移除???????

?removeChild(c.Context,?c)????}

}

可以發(fā)現(xiàn)cancelCtx類(lèi)型變量其實(shí)也是canceler類(lèi)型关翎,因?yàn)閏ancelCtx實(shí)現(xiàn)了canceler接口扛门。

Done方法和Err方法沒(méi)必要說(shuō)了,cancelCtx類(lèi)型的context在調(diào)用cancel方法時(shí)會(huì)設(shè)置取消原因纵寝,將done channel設(shè)置為一個(gè)關(guān)閉channel或者關(guān)閉channel论寨,然后將子節(jié)點(diǎn)context依次取消星立,如果有需要還會(huì)將當(dāng)前節(jié)點(diǎn)從父節(jié)點(diǎn)上移除。

WithCancel

WithCancel函數(shù)用來(lái)創(chuàng)建一個(gè)可取消的context葬凳,即cancelCtx類(lèi)型的context绰垂。WithCancel返回一個(gè)context和一個(gè)CancelFunc,調(diào)用CancelFunc即可觸發(fā)cancel操作火焰。直接看源碼:

type?CancelFunc?func()

func?WithCancel(parent?Context)?(ctx?Context,?cancel?CancelFunc)?{??

??c?:=?newCancelCtx(parent)????propagateCancel(parent,?&c)????

return?&c,?func()?{?c.cancel(true,?Canceled)?}

}

//?newCancelCtx?returns?an?initialized?cancelCtx.func?newCancelCtx(parent?Context)?cancelCtx?{???

?//?將parent作為父節(jié)點(diǎn)context生成一個(gè)新的子節(jié)點(diǎn)????

return?cancelCtx{Context:?parent}}

func?propagateCancel(parent?Context,?child?canceler)?{????if?parent.Done()?==?nil?{????????

//?parent.Done()返回nil表明父節(jié)點(diǎn)以上的路徑上沒(méi)有可取消的context????????return?//?parent?is?never?canceled????}????//?獲取最近的類(lèi)型為cancelCtx的祖先節(jié)點(diǎn)???

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

?//?將當(dāng)前子節(jié)點(diǎn)加入最近c(diǎn)ancelCtx祖先節(jié)點(diǎn)的children中???????????

?p.children[child]?=?struct{}{}???????

?}????????

p.mu.Unlock()???

?}?else?{????????go?func()?{???????????

?select?{????????????

case?<-parent.Done():???????????????

? ? ?????child.cancel(false,?parent.Err())???????????

?case?<-child.Done():????????????}????????}()????}

}

func?parentCancelCtx(parent?Context)?(*cancelCtx,?bool)?

{????for?{????????switch?c?:=?parent:(type)?{

????????case?*cancelCtx:????????????

????????return?c,?true????????

????????case?*timerCtx:????????????

????????return?&c.cancelCtx,?true? ? ? ? ? ? ? ? ????????case?*valueCtx:????????????

? ? ? ? ?parent?=?c.Context????????default:????????????

????????return?nil,?false????????}???

?}

}

之前說(shuō)到cancelCtx取消時(shí)劲装,會(huì)將后代節(jié)點(diǎn)中所有的cancelCtx都取消,propagateCancel即用來(lái)建立當(dāng)前節(jié)點(diǎn)與祖先節(jié)點(diǎn)這個(gè)取消關(guān)聯(lián)邏輯昌简。

如果parent.Done()返回nil占业,表明父節(jié)點(diǎn)以上的路徑上沒(méi)有可取消的context,不需要處理纯赎;

如果在context鏈上找到到cancelCtx類(lèi)型的祖先節(jié)點(diǎn)谦疾,則判斷這個(gè)祖先節(jié)點(diǎn)是否已經(jīng)取消已维,如果已經(jīng)取消就取消當(dāng)前節(jié)點(diǎn)六孵;否則將當(dāng)前節(jié)點(diǎn)加入到祖先節(jié)點(diǎn)的children列表拷泽。

否則開(kāi)啟一個(gè)協(xié)程趴俘,監(jiān)聽(tīng)parent.Done()和child.Done()哥纫,一旦parent.Done()返回的channel關(guān)閉硼一,即context鏈中某個(gè)祖先節(jié)點(diǎn)context被取消浸卦,則將當(dāng)前context也取消程帕。

這里或許有個(gè)疑問(wèn)该默,為什么是祖先節(jié)點(diǎn)而不是父節(jié)點(diǎn)瞳氓?這是因?yàn)楫?dāng)前context鏈可能是這樣的:


當(dāng)前cancelCtx的父節(jié)點(diǎn)context并不是一個(gè)可取消的context,也就沒(méi)法記錄children权均。

timerCtx

timerCtx是一種基于cancelCtx的context類(lèi)型顿膨,從字面上就能看出,這是一種可以定時(shí)取消的context叽赊。

type?timerCtx?struct?{????

????cancelCtx????timer?*time.Timer?//?Under?cancelCtx.mu.? ? ? ? ?????deadline?time.Time}

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

???return?c.deadline,?true}

func?(c?*timerCtx)?cancel(removeFromParent?bool,?err?error)?{????將內(nèi)部的cancelCtx取消????

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?{???????

// 取消計(jì)時(shí)器????????

c.timer.Stop()???????

?c.timer?=?nil????

}???

?c.mu.Unlock()

}

timerCtx內(nèi)部使用cancelCtx實(shí)現(xiàn)取消恋沃,另外使用定時(shí)器timer和過(guò)期時(shí)間deadline實(shí)現(xiàn)定時(shí)取消的功能。timerCtx在調(diào)用cancel方法必指,會(huì)先將內(nèi)部的cancelCtx取消囊咏,如果需要?jiǎng)t將自己從cancelCtx祖先節(jié)點(diǎn)上移除,最后取消計(jì)時(shí)器塔橡。

WithDeadline

WithDeadline返回一個(gè)基于parent的可取消的context梅割,并且其過(guò)期時(shí)間deadline不晚于所設(shè)置時(shí)間d。

func?WithDeadline(parent?Context,?d?time.Time)?(Context,?CancelFunc)?{???

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

//?建立新建context與可取消context祖先節(jié)點(diǎn)的取消關(guān)聯(lián)關(guān)系????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)?}

}

如果父節(jié)點(diǎn)parent有過(guò)期時(shí)間并且過(guò)期時(shí)間早于給定時(shí)間d葛家,那么新建的子節(jié)點(diǎn)context無(wú)需設(shè)置過(guò)期時(shí)間户辞,使用WithCancel創(chuàng)建一個(gè)可取消的context即可;

否則癞谒,就要利用parent和過(guò)期時(shí)間d創(chuàng)建一個(gè)定時(shí)取消的timerCtx底燎,并建立新建context與可取消context祖先節(jié)點(diǎn)的取消關(guān)聯(lián)關(guān)系刃榨,接下來(lái)判斷當(dāng)前時(shí)間距離過(guò)期時(shí)間d的時(shí)長(zhǎng)dur:

如果dur小于0,即當(dāng)前已經(jīng)過(guò)了過(guò)期時(shí)間双仍,則直接取消新建的timerCtx枢希,原因?yàn)镈eadlineExceeded;

否則朱沃,為新建的timerCtx設(shè)置定時(shí)器苞轿,一旦到達(dá)過(guò)期時(shí)間即取消當(dāng)前timerCtx。

WithTimeout

與WithDeadline類(lèi)似逗物,WithTimeout也是創(chuàng)建一個(gè)定時(shí)取消的context搬卒,只不過(guò)WithDeadline是接收一個(gè)過(guò)期時(shí)間點(diǎn),而WithTimeout接收一個(gè)相對(duì)當(dāng)前時(shí)間的過(guò)期時(shí)長(zhǎng)timeout:

func?WithTimeout(parent?Context,?timeout?time.Duration)?(Context,?CancelFunc)?{???

?return?WithDeadline(parent,?time.Now().Add(timeout))

}

context的使用

首先使用context實(shí)現(xiàn)文章開(kāi)頭done channel的例子來(lái)示范一下如何更優(yōu)雅實(shí)現(xiàn)協(xié)程間取消信號(hào)的同步:

func?main()?{????

messages?:=?make(chan?int,?10)????//?producer????

for?i?:=?0;?i?<?10;?i++?{????????messages?<-?i????}???

?ctx,?cancel?:=?context.WithTimeout(context.Background(),?5*time.Second)????

//?consumer????

go?func(ctx?context.Context)?{????????ticker?:=?time.NewTicker(1?*?time.Second)????????

for?_?=?range?ticker.C?{????????????select?{????????????case?<-ctx.Done():????????????????fmt.Println("child?process?interrupt...")????????????????return????????????default:???????????????

?fmt.Printf("send?message:?%d\n",?<-messages)????????????}???????

?}???

?}(ctx)????

defer?close(messages)????

defer?cancel()????select?{????

case?<-ctx.Done():????????

time.Sleep(1?*?time.Second)????????fmt.Println("main?process?exit!")????}

}

這個(gè)例子中敬察,只要讓子線(xiàn)程監(jiān)聽(tīng)主線(xiàn)程傳入的ctx秀睛,一旦ctx.Done()返回空channel尔当,子線(xiàn)程即可取消執(zhí)行任務(wù)莲祸。但這個(gè)例子還無(wú)法展現(xiàn)context的傳遞取消信息的強(qiáng)大優(yōu)勢(shì)。

閱讀過(guò)net/http包源碼的朋友可能注意到在實(shí)現(xiàn)http server時(shí)就用到了context, 下面簡(jiǎn)單分析一下椭迎。

1锐帜、首先Server在開(kāi)啟服務(wù)時(shí)會(huì)創(chuàng)建一個(gè)valueCtx,存儲(chǔ)了server的相關(guān)信息,之后每建立一條連接就會(huì)開(kāi)啟一個(gè)協(xié)程畜号,并攜帶此valueCtx缴阎。

func?(srv?*Server)?Serve(l?net.Listener)?error?{?

???...????

var?tempDelay?time.Duration?????//?how?long?to?sleep?on?accept?failure????

baseCtx?:=?context.Background()?//?base?is?always?background,?per?Issue?16220????

ctx?:=?context.WithValue(baseCtx,?ServerContextKey,?srv)????for?{???????

?rw,?e?:=?l.Accept()????????...???????

?tempDelay?=?0????????

c?:=?srv.newConn(rw)???????

?c.setState(c.rwc,?StateNew)?//?before?Serve?can?return????????

go?c.serve(ctx)????}

}

2、建立連接之后會(huì)基于傳入的context創(chuàng)建一個(gè)valueCtx用于存儲(chǔ)本地地址信息简软,之后在此基礎(chǔ)上又創(chuàng)建了一個(gè)cancelCtx蛮拔,然后開(kāi)始從當(dāng)前連接中讀取網(wǎng)絡(luò)請(qǐng)求,每當(dāng)讀取到一個(gè)請(qǐng)求則會(huì)將該cancelCtx傳入痹升,用以傳遞取消信號(hào)建炫。一旦連接斷開(kāi),即可發(fā)送取消信號(hào)疼蛾,取消所有進(jìn)行中的網(wǎng)絡(luò)請(qǐng)求肛跌。

func?(c?*conn)?serve(ctx?context.Context)?{???

?c.remoteAddr?=?c.rwc.RemoteAddr().String()????

ctx?=?context.WithValue(ctx,?LocalAddrContextKey,?c.rwc.LocalAddr())???

?...???

?ctx,cancelCtx?:=?context.WithCancel(ctx)????

c.cancelCtx?=?cancelCtx????defer?cancelCtx()???

?...????

for?{????????w,?err?:=?c.readRequest(ctx)???????

?...???????

?serverHandler{c.server}.ServeHTTP(w,?w.req)???????

?...????}

}

3、讀取到請(qǐng)求之后察郁,會(huì)再次基于傳入的context創(chuàng)建新的cancelCtx,并設(shè)置到當(dāng)前請(qǐng)求對(duì)象req上衍慎,同時(shí)生成的response對(duì)象中cancelCtx保存了當(dāng)前context取消方法。

func?(c?*conn)?readRequest(ctx?context.Context)?(w?*response,?err?error)?{??

??...???

?req,?err?:=?readRequest(c.bufr,?keepHostHeader)????

...????ctx,?cancelCtx?:=?context.WithCancel(ctx)????

req.ctx?=?ctx????

...????

w?=?&response{????????

conn:??????????c,????????

cancelCtx:?????cancelCtx,???????

?req:???????????req,???????

?reqBody:???????req.Body,???????

?handlerHeader:?make(Header),????????

contentLength:?-1,????????

closeNotifyCh:?make(chan?bool,?1),????????

//?We?populate?these?ahead?of?time?so?we're?not????????

//?reading?from?req.Header?after?their?Handler?starts????????

//?and?maybe?mutates?it?(Issue?14940)????????

wants10KeepAlive:?req.wantsHttp10KeepAlive(),???????

?wantsClose:???????req.wantsClose(),????}???

?...???

?return?w,?nil}

這樣處理的目的主要有以下幾點(diǎn):

一旦請(qǐng)求超時(shí)皮钠,即可中斷當(dāng)前請(qǐng)求稳捆;

在處理構(gòu)建response過(guò)程中如果發(fā)生錯(cuò)誤,可直接調(diào)用response對(duì)象的cancelCtx方法結(jié)束當(dāng)前請(qǐng)求麦轰;

在處理構(gòu)建response完成之后乔夯,調(diào)用response對(duì)象的cancelCtx方法結(jié)束當(dāng)前請(qǐng)求期虾。

在整個(gè)server處理流程中,使用了一條context鏈貫穿Server驯嘱、Connection镶苞、Request,不僅將上游的信息共享給下游任務(wù)鞠评,同時(shí)實(shí)現(xiàn)了上游可發(fā)送取消信號(hào)取消所有下游任務(wù)茂蚓,而下游任務(wù)自行取消不會(huì)影響上游任務(wù)。

總結(jié)

context主要用于父子任務(wù)之間的同步取消信號(hào)剃幌,本質(zhì)上是一種協(xié)程調(diào)度的方式聋涨。另外在使用context時(shí)有兩點(diǎn)值得注意:上游任務(wù)僅僅使用context通知下游任務(wù)不再需要,但不會(huì)直接干涉和中斷下游任務(wù)的執(zhí)行负乡,由下游任務(wù)自行決定后續(xù)的處理操作牍白,也就是說(shuō)context的取消操作是無(wú)侵入的;context是線(xiàn)程安全的抖棘,因?yàn)閏ontext本身是不可變的(immutable)茂腥,因此可以放心地在多個(gè)協(xié)程中傳遞使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末切省,一起剝皮案震驚了整個(gè)濱河市最岗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌朝捆,老刑警劉巖般渡,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異芙盘,居然都是意外死亡驯用,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)儒老,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蝴乔,“玉大人,你說(shuō)我怎么就攤上這事贷盲√哉猓” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵巩剖,是天一觀的道長(zhǎng)铝穷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)佳魔,這世上最難降的妖魔是什么曙聂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮鞠鲜,結(jié)果婚禮上宁脊,老公的妹妹穿的比我還像新娘断国。我一直安慰自己,他們只是感情好榆苞,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布稳衬。 她就那樣靜靜地躺著,像睡著了一般坐漏。 火紅的嫁衣襯著肌膚如雪薄疚。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天赊琳,我揣著相機(jī)與錄音街夭,去河邊找鬼。 笑死躏筏,一個(gè)胖子當(dāng)著我的面吹牛板丽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趁尼,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼埃碱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了弱卡?” 一聲冷哼從身側(cè)響起乃正,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎婶博,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體荧飞,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凡人,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叹阔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挠轴。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖耳幢,靈堂內(nèi)的尸體忽然破棺而出岸晦,到底是詐尸還是另有隱情,我是刑警寧澤睛藻,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布启上,位于F島的核電站,受9級(jí)特大地震影響店印,放射性物質(zhì)發(fā)生泄漏冈在。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一按摘、第九天 我趴在偏房一處隱蔽的房頂上張望包券。 院中可真熱鬧纫谅,春花似錦、人聲如沸溅固。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侍郭。三九已至盹牧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間励幼,已是汗流浹背汰寓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苹粟,地道東北人有滑。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嵌削,于是被迫代替她去往敵國(guó)和親毛好。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 前言 首先解答上一篇文章一文帶你快速入門(mén)context中留下的疑惑苛秕,為什么要defer cancelFunc()肌访?...
    滅BUG閱讀 477評(píng)論 0 0
  • 起因 最近學(xué)習(xí)golang框架的時(shí)候發(fā)現(xiàn)許多地方都用到了context的概念,比如grpc請(qǐng)求 etcd訪(fǎng)問(wèn)等許...
    Kathent閱讀 1,643評(píng)論 0 3
  • context包專(zhuān)門(mén)用來(lái)簡(jiǎn)化處理單個(gè)請(qǐng)求的多個(gè)goroutine之間與請(qǐng)求域的數(shù)據(jù)艇劫、取消信號(hào)吼驶、截止時(shí)間等相關(guān)操作。...
    wz998閱讀 3,741評(píng)論 0 3
  • Context 通常被譯作上下文店煞,一般理解為程序單元的一個(gè)運(yùn)行狀態(tài)蟹演、現(xiàn)場(chǎng)、快照顷蟀,而翻譯中上下文又很好地詮釋了它的本...
    Asphalt7閱讀 546評(píng)論 0 0
  • 引言 context 是 Go 中廣泛使用的程序包酒请,由 Google 官方開(kāi)發(fā),在 1.7 版本引入鸣个。它用來(lái)簡(jiǎn)化在...
    51reboot閱讀 3,505評(píng)論 0 10