深入理解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è)例子中,只要讓子線程監(jiān)聽(tīng)主線程傳入的ctx,一旦ctx.Done()返回空channel,子線程即可取消執(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是線程安全的,因?yàn)閏ontext本身是不可變的(immutable),因此可以放心地在多個(gè)協(xié)程中傳遞使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,640評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,957評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,682評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,011評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,183評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,435評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,665評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,838評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,251評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,588評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,379評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,627評(píng)論 2 380

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

  • 前言 首先解答上一篇文章一文帶你快速入門(mén)context中留下的疑惑,為什么要defer cancelFunc()?...
    滅BUG閱讀 500評(píng)論 0 0
  • 起因 最近學(xué)習(xí)golang框架的時(shí)候發(fā)現(xiàn)許多地方都用到了context的概念,比如grpc請(qǐng)求 etcd訪問(wèn)等許...
    Kathent閱讀 1,653評(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,792評(píng)論 0 3
  • Context 通常被譯作上下文,一般理解為程序單元的一個(gè)運(yùn)行狀態(tài)、現(xiàn)場(chǎng)、快照,而翻譯中上下文又很好地詮釋了它的本...
    Asphalt7閱讀 571評(píng)論 0 0
  • 引言 context 是 Go 中廣泛使用的程序包,由 Google 官方開(kāi)發(fā),在 1.7 版本引入。它用來(lái)簡(jiǎn)化在...
    51reboot閱讀 3,573評(píng)論 0 10