1.在后端服務(wù)開發(fā)中,如過(guò)一個(gè)HTTP請(qǐng)求,請(qǐng)求一致占用,將會(huì)帶來(lái)大的性能影響,所以需要為每個(gè)請(qǐng)求加上超時(shí)設(shè)置
烏達(dá)網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),烏達(dá)網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為烏達(dá)近千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營(yíng)銷網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的烏達(dá)做網(wǎng)站的公司定做!
2.在go語(yǔ)言中利用 context 進(jìn)行上下文控制,要想達(dá)到精確時(shí)間控制,如下:
3.同時(shí)我們也可以利用context的context.WithDeadline()函數(shù)來(lái)進(jìn)行超時(shí)控制
最近聽了一位同事的分享,他說(shuō)如果一個(gè)人不能把它所研究的項(xiàng)目/概念用十分簡(jiǎn)單的話表述清楚,那么就說(shuō)明他并沒(méi)有真正理解這個(gè)項(xiàng)目。然后他拿物理中粒子的自旋進(jìn)行舉例,有人向教授請(qǐng)教相關(guān)概念,教授說(shuō):我需要思考一下如何用淺顯的話進(jìn)行表述。稍許之后,教授說(shuō):很抱歉,可能我只能用非常復(fù)雜的公式和概念向你解釋了。這說(shuō)明了可能人類對(duì)于這一現(xiàn)象的本質(zhì)并沒(méi)有理解。
結(jié)合我的上一篇文章 最近的一些感悟 - 體系的力量 ,這更加說(shuō)明了我們?cè)趯W(xué)習(xí)的時(shí)候其實(shí)就是撥開繁雜的迷霧,去窺探一個(gè)概念、一個(gè)項(xiàng)目、一個(gè)體系它最核心的本質(zhì)。計(jì)算機(jī)是一門科學(xué),由人類創(chuàng)造,所以我們應(yīng)該是能力用簡(jiǎn)單的話將它表述清楚的。所以我也會(huì)以這樣的要求來(lái)進(jìn)行寫作,力求用最簡(jiǎn)單、清晰的語(yǔ)言來(lái)描述。
今天的內(nèi)容是golang中的context包中的Context接口。
context.Context本身為interface(接口),主要用于父協(xié)程關(guān)閉后可以同步關(guān)閉所有子孫協(xié)程,是一種并發(fā)控制/協(xié)程同步的重要手段。
那么我們實(shí)現(xiàn)Context,只需要實(shí)現(xiàn)Done, Err, Value, Deadline四個(gè)方法即可:
context包中提供了如withCancel, withTimeout等一系列方法用于創(chuàng)建子context。context之間呈樹狀結(jié)構(gòu),當(dāng)傳遞事件(如取消)/數(shù)據(jù)時(shí),可以遞歸地從上到下進(jìn)行傳遞以控制子孫協(xié)程。
WithCancel:
Cancel:
WithDeadline:
WithTimeout:實(shí)際上也是調(diào)用WithDeadline
golang在1.6.2的時(shí)候還沒(méi)有自己的context,在1.7的版本中就把golang.org/x/net/context包被加入到了官方的庫(kù)中。中文譯作“上下文”,它主要包含了goroutine 的運(yùn)行狀態(tài)、環(huán)境等信息。
context 主要用來(lái)在 goroutine 之間傳遞上下文信息,包括:同步信號(hào)、超時(shí)時(shí)間、截止時(shí)間、請(qǐng)求相關(guān)值等。
該接口定義了四個(gè)需要實(shí)現(xiàn)的方法:
如果有個(gè)網(wǎng)絡(luò)請(qǐng)求Request,然后這個(gè)請(qǐng)求又可以開啟多個(gè)goroutine做一些事情,當(dāng)這個(gè)網(wǎng)絡(luò)請(qǐng)求出現(xiàn)異常和超時(shí)時(shí),這個(gè)請(qǐng)求結(jié)束了,這時(shí)候就可以通過(guò)context來(lái)跟蹤這些goroutine,并且通過(guò)Context來(lái)取消他們,然后系統(tǒng)才可回收所占用的資源。
為了更方便的創(chuàng)建Context,包里頭定義了Background來(lái)作為所有Context的根,它是一個(gè)emptyCtx的實(shí)例。
Background返回一個(gè)非空的Context。它永遠(yuǎn)不會(huì)被取消。它通常用來(lái)初始化和測(cè)試使用,作為一個(gè)頂層的context,也就是說(shuō)一般我們創(chuàng)建的context都是基于Background。
TODO返回一個(gè)非空的Context。當(dāng)不清楚要使用哪個(gè)上下文的時(shí)候可以使用TODO。
他們兩個(gè)本質(zhì)上都是emptyCtx結(jié)構(gòu)體類型,是一個(gè)不可取消,沒(méi)有設(shè)置截止時(shí)間,沒(méi)有攜帶任何值的Context。
有了如上的根Context,那么是如何衍生更多的子Context的呢?這就要靠context包為我們提供的With系列的函數(shù)了。
通過(guò)這些函數(shù),就創(chuàng)建了一顆Context樹,樹的每個(gè)節(jié)點(diǎn)都可以有任意多個(gè)子節(jié)點(diǎn),節(jié)點(diǎn)層級(jí)可以有任意多個(gè)。
WithCancel函數(shù),最常用的派生 context 方法。該方法接受一個(gè)父 context。父 context 可以是一個(gè) background context 或其他 context。
WithDeadline函數(shù),該方法會(huì)創(chuàng)建一個(gè)帶有 deadline 的 context。當(dāng) deadline 到期后,該 context 以及該 context 的可能子 context 會(huì)受到 cancel 通知。另外,如果 deadline 前調(diào)用 cancelFunc 則會(huì)提前發(fā)送取消通知。
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)到,一般我們想要通過(guò)上下文來(lái)傳遞數(shù)據(jù)時(shí),可以通過(guò)這個(gè)方法,如我們需要tarce追蹤系統(tǒng)調(diào)用棧的時(shí)候。
使用Context的程序應(yīng)遵循以下規(guī)則,以使各個(gè)包之間的接口保持一致:
1.不要將 Context 塞到結(jié)構(gòu)體里。直接將 Context 類型作為函數(shù)的第一參數(shù),而且一般都命名為 ctx。
2.不要向函數(shù)傳入一個(gè) nil 的 context,如果你實(shí)在不知道傳什么,標(biāo)準(zhǔn)庫(kù)給你準(zhǔn)備好了一個(gè) context:todo。
3.不要把本應(yīng)該作為函數(shù)參數(shù)的類型塞到 context 中,context 存儲(chǔ)的應(yīng)該是一些共同的數(shù)據(jù)。例如:登陸的 session、cookie 等。
4.同一個(gè) context 可能會(huì)被傳遞到多個(gè) goroutine,別擔(dān)心,context 是并發(fā)安全的。
go從1.7版本之后開始引入了context,它的作用主要是傳遞上下文信息,像一個(gè)大容器,里面也可以存儲(chǔ)k-v等數(shù)據(jù)。之后很多標(biāo)準(zhǔn)庫(kù)基本上都把函數(shù)的第一個(gè)參數(shù)默認(rèn)為context參數(shù)
作為天然適合寫并發(fā)的語(yǔ)言,在處理一個(gè)請(qǐng)求適合往往都會(huì)開啟多個(gè)goroutine,那么這些goroutine之前通常需要共享一些共享信息,這個(gè)時(shí)候context就派上用場(chǎng)了
為什么需要context
在go服務(wù)器中,對(duì)于每個(gè)請(qǐng)求的request都是在單獨(dú)的goroutine中進(jìn)行的,處理一個(gè)request也可能設(shè)計(jì)多個(gè)goroutine之間的交互, 使用context可以使開發(fā)者方便的在這些goroutine里傳遞request相關(guān)的數(shù)據(jù)、取消goroutine的signal或截止日期
在并發(fā)程序中,由于超時(shí)、取消操作或者一些異常情況,往往需要進(jìn)行搶占操作或者中斷后續(xù)操作。熟悉channel的朋友應(yīng)該都見過(guò)使用done channel來(lái)處理此類問(wèn)題。比如以下這個(gè)例子:
上述例子中定義了一個(gè)buffer為0的channel done, 子協(xié)程運(yùn)行著定時(shí)任務(wù)。如果主協(xié)程需要在某個(gè)時(shí)刻發(fā)送消息通知子協(xié)程中斷任務(wù)退出,那么就可以讓子協(xié)程監(jiān)聽這個(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)聽這兩個(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接口結(jié)構(gòu),看起來(lái)非常簡(jiǎn)單。
}
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ù)樹的額外信息。而context所包含的額外信息鍵值對(duì)是如何存儲(chǔ)的呢?其實(shí)可以想象一顆樹,樹的每個(gè)節(jié)點(diǎn)可能攜帶一組鍵值對(duì),如果當(dāng)前節(jié)點(diǎn)上無(wú)法找到key所對(duì)應(yīng)的值,就會(huì)向上去父節(jié)點(diǎn)里找,直到根節(jié)點(diǎn)。
emptyCtx
emptyCtx是一個(gè)int類型的變量,但實(shí)現(xiàn)了context的接口。emptyCtx沒(méi)有超時(shí)時(shí)間,不能取消,也不能存儲(chǔ)任何額外信息,所以emptyCtx用來(lái)作為context樹的根節(jié)點(diǎn)。
Background和TODO只是用于不同場(chǎng)景下: Background通常被用于主函數(shù)、初始化以及測(cè)試中,作為一個(gè)頂層的context,也就是說(shuō)一般我們創(chuàng)建的context都是基于Background;而TODO是在不確定使用什么context的時(shí)候才會(huì)使用。
用法 :
1.通過(guò)endless包實(shí)現(xiàn)
2.通過(guò)shutdown實(shí)現(xiàn)
在go 1.8.x后,golang在http里加入了shutdown方法,用來(lái)控制優(yōu)雅退出。什么是優(yōu)雅退出? 簡(jiǎn)單說(shuō)就是不處理新請(qǐng)求,但是會(huì)處理正在進(jìn)行的請(qǐng)求,把舊請(qǐng)求都處理完,也就是都response之后,那么就退出。
shutdown通過(guò)context上下文實(shí)現(xiàn) 。
社區(qū)里不少http graceful動(dòng)態(tài)重啟,平滑重啟的庫(kù),大多是基于http.shutdown做的。平滑啟動(dòng)的原理很簡(jiǎn)單,fork子進(jìn)程,繼承l(wèi)isten fd, 老進(jìn)程優(yōu)雅退出。
3.context原理
context 是 Go 并發(fā)編程中常用到一種編程模式。
在并發(fā)程序中,由于超時(shí)、取消操作或者一些異常情況,往往需要進(jìn)行搶占操作或者中斷后續(xù)操作。熟悉 channel 的朋友應(yīng)該都見過(guò)使用 done channel 來(lái)處理此類問(wèn)題。比如以下這個(gè)例子:
上述例子中定義了一個(gè) buffer 為0的 channel done , 子協(xié)程運(yùn)行著定時(shí)任務(wù)。如果主協(xié)程需要在某個(gè)時(shí)刻發(fā)送消息通知子協(xié)程中斷任務(wù)退出,那么就可以讓子協(xié)程監(jiān)聽這個(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)聽這兩個(gè) done channel 。嗯,這樣其實(shí)好像也還行哈。但是如果層級(jí)更深,如果這些子任務(wù)還有子任務(wù),那么使用 done channel 的方式將會(huì)變得非常繁瑣且混亂。
我們需要一種優(yōu)雅的方案來(lái)實(shí)現(xiàn)這樣一種機(jī)制:
這個(gè)時(shí)候 context 就派上用場(chǎng)了。
我們首先看看 context 的結(jié)構(gòu)設(shè)計(jì)和實(shí)現(xiàn)原理。
先看 Context 接口結(jié)構(gòu),看起來(lái)非常簡(jiǎn)單。
Context 接口包含四個(gè)方法:
可以看到 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ù)樹的額外信息。而 context 所包含的額外信息鍵值對(duì)是如何存儲(chǔ)的呢?其實(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ō)到。
emptyCtx 是一個(gè) int 類型的變量,但實(shí)現(xiàn)了 context 的接口。 emptyCtx 沒(méi)有超時(shí)時(shí)間,不能取消,也不能存儲(chǔ)任何額外信息,所以 emptyCtx 用來(lái)作為 context 樹的根節(jié)點(diǎn)。
但我們一般不會(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 類型: valueCtx 和 cancelCtx 。
valueCtx 利用一個(gè) Context 類型的變量來(lái)表示父節(jié)點(diǎn) context ,所以當(dāng)前 context 繼承了父 context 的所有信息; valueCtx 類型還攜帶一組鍵值對(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 用以向 context 添加鍵值對(duì):
這里添加鍵值對(duì)不是在原 context 結(jié)構(gòu)體上直接添加,而是以此 context 作為父節(jié)點(diǎn),重新創(chuàng)建一個(gè)新的 valueCtx 子節(jié)點(diǎn),將鍵值對(duì)添加在子節(jié)點(diǎn)上,由此形成一條 context 鏈。獲取 value 的過(guò)程就是在這條 context 鏈上由尾部上前搜尋:
跟 valueCtx 類似, 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)的方法:
可以發(fā)現(xiàn) cancelCtx 類型變量其實(shí)也是 canceler 類型,因?yàn)? cancelCtx 實(shí)現(xiàn)了 canceler 接口。 Done 方法和 Err 方法沒(méi)必要說(shuō)了, cancelCtx 類型的 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 函數(shù)用來(lái)創(chuàng)建一個(gè)可取消的 context ,即 cancelCtx 類型的 context 。 WithCancel 返回一個(gè) context 和一個(gè) CancelFunc ,調(diào)用 CancelFunc 即可觸發(fā) cancel 操作。直接看源碼:
之前說(shuō)到 cancelCtx 取消時(shí),會(huì)將后代節(jié)點(diǎn)中所有的 cancelCtx 都取消, propagateCancel 即用來(lái)建立當(dāng)前節(jié)點(diǎn)與祖先節(jié)點(diǎn)這個(gè)取消關(guān)聯(lián)邏輯。
這里或許有個(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 是一種基于 cancelCtx 的 context 類型,從字面上就能看出,這是一種可以定時(shí)取消的 context 。
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 返回一個(gè)基于 parent 的可取消的 context ,并且其過(guò)期時(shí)間 deadline 不晚于所設(shè)置時(shí)間 d 。
與 WithDeadline 類似, WithTimeout 也是創(chuàng)建一個(gè)定時(shí)取消的 context ,只不過(guò) WithDeadline 是接收一個(gè)過(guò)期時(shí)間點(diǎn),而 WithTimeout 接收一個(gè)相對(duì)當(dāng)前時(shí)間的過(guò)期時(shí)長(zhǎng) timeout :
首先使用 context 實(shí)現(xiàn)文章開頭 done channel 的例子來(lái)示范一下如何更優(yōu)雅實(shí)現(xiàn)協(xié)程間取消信號(hào)的同步:
這個(gè)例子中,只要讓子線程監(jiān)聽主線程傳入的 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 在開啟服務(wù)時(shí)會(huì)創(chuàng)建一個(gè) valueCtx ,存儲(chǔ)了 server 的相關(guān)信息,之后每建立一條連接就會(huì)開啟一個(gè)協(xié)程,并攜帶此 valueCtx 。
2、建立連接之后會(huì)基于傳入的 context 創(chuàng)建一個(gè) valueCtx 用于存儲(chǔ)本地地址信息,之后在此基礎(chǔ)上又創(chuàng)建了一個(gè) cancelCtx ,然后開始從當(dāng)前連接中讀取網(wǎng)絡(luò)請(qǐng)求,每當(dāng)讀取到一個(gè)請(qǐng)求則會(huì)將該 cancelCtx 傳入,用以傳遞取消信號(hào)。一旦連接斷開,即可發(fā)送取消信號(hào),取消所有進(jìn)行中的網(wǎng)絡(luò)請(qǐng)求。
3、讀取到請(qǐng)求之后,會(huì)再次基于傳入的 context 創(chuàng)建新的 cancelCtx ,并設(shè)置到當(dāng)前請(qǐng)求對(duì)象 req 上,同時(shí)生成的 response 對(duì)象中 cancelCtx 保存了當(dāng)前 context 取消方法。
這樣處理的目的主要有以下幾點(diǎn):
在整個(gè) server 處理流程中,使用了一條 context 鏈貫穿 Server 、 Connection 、 Request ,不僅將上游的信息共享給下游任務(wù),同時(shí)實(shí)現(xiàn)了上游可發(fā)送取消信號(hào)取消所有下游任務(wù),而下游任務(wù)自行取消不會(huì)影響上游任務(wù)。
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)? context 本身是不可變的( immutable ),因此可以放心地在多個(gè)協(xié)程中傳遞使用。