Go 協(xié)程超時(shí)控制的實(shí)現(xiàn)
Go 協(xié)程超時(shí)控制
- Select 阻塞方式
- Context 方式
先說(shuō)個(gè)場(chǎng)景:
假設(shè)業(yè)務(wù)中 A 服務(wù)需要調(diào)用 服務(wù)B,要求設(shè)置 5s 超時(shí),那么如何優(yōu)雅實(shí)現(xiàn)?
Select 超時(shí)控制
考慮是否可以用 select + time.After 方式進(jìn)行實(shí)現(xiàn)
這里主要利用的是通道在攜程之間通信的特點(diǎn),當(dāng)程序調(diào)用成功后,會(huì)向通道中發(fā)送信號(hào)。沒(méi)調(diào)用成功前,通道會(huì)阻塞。
select { case res := <-c2: fmt.Println(res) case <-time.After(time.Second * 3): fmt.Println("timeout 2") }
當(dāng) c2 通道中有數(shù)據(jù)時(shí),并且超時(shí)時(shí)間沒(méi)有達(dá)到 3s,走 case res := <-c2 這個(gè)業(yè)務(wù)邏輯,當(dāng)超時(shí)時(shí)間達(dá)到 3s , 走的 case <-time.After(time.Second * 3) 這個(gè)業(yè)務(wù)邏輯, 這樣就可以實(shí)現(xiàn)超時(shí) 3s 的控制。
res:= <-c2 是因?yàn)閏hannel 可以實(shí)現(xiàn)阻塞,那么 time.After 為啥可以阻塞呢?
看 After 源碼。sleep.go 可以看到其實(shí)也是 channel
func After(d Duration) <-chan Time { return NewTimer(d).C }
完整代碼示例:
package timeout import ( "fmt" "testing" "time" ) func TestSelectTimeOut(t *testing.T) { // 在這個(gè)例子中, 假設(shè)我們執(zhí)行了一個(gè)外部調(diào)用, 2秒之后將結(jié)果寫(xiě)入c1 c1 := make(chan string, 1) go func() { time.Sleep(time.Second * 2) c1 <- "result 1" }() // 這里使用select來(lái)實(shí)現(xiàn)超時(shí), `res := <-c1`等待通道結(jié)果, // `<- Time.After`則在等待1秒后返回一個(gè)值, 因?yàn)閟elect首先 // 執(zhí)行那些不再阻塞的case, 所以這里會(huì)執(zhí)行超時(shí)程序, 如果 // `res := <-c1`超過(guò)1秒沒(méi)有執(zhí)行的話 select { case res := <-c1: fmt.Println(res) case <-time.After(time.Second * 1): fmt.Println("timeout 1") } // 如果我們將超時(shí)時(shí)間設(shè)為3秒, 這個(gè)時(shí)候`res := <-c2`將在 // 超時(shí)case之前執(zhí)行, 從而能夠輸出寫(xiě)入通道c2的值 c2 := make(chan string, 1) go func() { time.Sleep(time.Second * 2) c2 <- "result 2" }() select { case res := <-c2: fmt.Println(res) case <-time.After(time.Second * 3): fmt.Println("timeout 2") } }
運(yùn)行結(jié)果:
=== RUN TestSelectTimeOut
timeout 1
result 2
--- PASS: TestSelectTimeOut (3.00s)
PASS
go timer 計(jì)時(shí)器
這個(gè)是 timer 類(lèi)似的計(jì)時(shí)器實(shí)現(xiàn),通用也是通過(guò)通道來(lái)發(fā)送數(shù)據(jù)。
package main import "time" import "fmt" func main() { // Ticker使用和Timer相似的機(jī)制, 同樣是使用一個(gè)通道來(lái)發(fā)送數(shù)據(jù)。 // 這里我們使用range函數(shù)來(lái)遍歷通道數(shù)據(jù), 這些數(shù)據(jù)每隔500毫秒被 // 發(fā)送一次, 這樣我們就可以接收到 ticker := time.NewTicker(time.Millisecond * 500) go func() { for t := range ticker.C { fmt.Println("Tick at", t) } }() // Ticker和Timer一樣可以被停止。 一旦Ticker停止后, 通道將不再 // 接收數(shù)據(jù), 這里我們將在1500毫秒之后停止 time.Sleep(time.Millisecond * 1500) ticker.Stop() fmt.Println("Ticker stopped") }
go context
context 監(jiān)聽(tīng)是否有 IO 操作,開(kāi)始從當(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)求。
go func(ctx context.Context, info *Info) { timeLimit := 120 timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeLimit)*time.Millisecond) defer func() { cancel() wg.Done() }() resp := DoHttp(timeoutCtx, info.req) }(ctx, info)
關(guān)鍵看業(yè)務(wù)代碼: resp := DoHttp(timeoutCtx, info.req) 業(yè)務(wù)代碼中包含 http 調(diào)用 NewRequestWithContext
req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(paramString))
上面的代碼,設(shè)置了過(guò)期時(shí)間,當(dāng)DoHttp(timeoutCtx, info.req) 處理時(shí)間超過(guò)超時(shí)時(shí)間時(shí),會(huì)自動(dòng)截止,并且打印 context deadline exceeded。
看個(gè)代碼:
package main import ( "context" "fmt" "testing" "time" ) func TestTimerContext(t *testing.T) { now := time.Now() later, _ := time.ParseDuration("10s") ctx, cancel := context.WithDeadline(context.Background(), now.Add(later)) defer cancel() go Monitor(ctx) time.Sleep(20 * time.Second) } func Monitor(ctx context.Context) { select { case <-ctx.Done(): fmt.Println(ctx.Err()) case <-time.After(20 * time.Second): fmt.Println("stop monitor") } }
運(yùn)行結(jié)果:
=== RUN TestTimerContext
context deadline exceeded
--- PASS: TestTimerContext (20.00s)
PASS
Context 接口有如下:
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
- Deadline — 返回 context.Context 被取消的時(shí)間,也就是完成工作的截止日期;
- Done — 返回一個(gè) Channel,這個(gè) Channel 會(huì)在當(dāng)前工作完成或者上下文被取消之后關(guān)閉,多次調(diào)用 Done 方法會(huì)返回同一個(gè) Channel;
- Err — 返回 context.Context 結(jié)束的原因,它只會(huì)在 Done 返回的 Channel 被關(guān)閉時(shí)才會(huì)返回非空的值;
- 如果 context.Context 被取消,會(huì)返回 Canceled 錯(cuò)誤;
- 如果 context.Context 超時(shí),會(huì)返回 DeadlineExceeded 錯(cuò)誤;
- Value — 從 context.Context 中獲取鍵對(duì)應(yīng)的值,對(duì)于同一個(gè)上下文來(lái)說(shuō),多次調(diào)用 Value 并傳入相同的 Key 會(huì)返回相同的結(jié)果,該方法可以用來(lái)傳遞請(qǐng)求特定的數(shù)據(jù);
到此這篇關(guān)于Go 協(xié)程超時(shí)控制的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Go 協(xié)程超時(shí)控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go?doudou開(kāi)發(fā)gRPC服務(wù)快速上手實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了go?doudou開(kāi)發(fā)gRPC服務(wù)快速上手實(shí)現(xiàn)過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12GoLang RabbitMQ TTL與死信隊(duì)列以及延遲隊(duì)列詳細(xì)講解
這篇文章主要介紹了GoLang RabbitMQ TTL與死信隊(duì)列以及延遲隊(duì)列,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12使用Golong實(shí)現(xiàn)JWT身份驗(yàn)證的詳細(xì)過(guò)程
JWT提供了一種強(qiáng)大而靈活的方法來(lái)處理Web應(yīng)用程序中的身份驗(yàn)證和授權(quán),本教程將引導(dǎo)您逐步實(shí)現(xiàn)Go應(yīng)用程序中的JWT身份驗(yàn)證過(guò)程,感興趣的朋友跟隨小編一起看看吧2024-03-03基于Go語(yǔ)言簡(jiǎn)單實(shí)現(xiàn)事件管理器
在編程中,事件管理器是一種常見(jiàn)的工具,用于通過(guò)通知來(lái)觸發(fā)操作,本文將介紹一個(gè)簡(jiǎn)單的Go事件管理器的實(shí)現(xiàn),并通過(guò)異步改進(jìn)提高其性能,感興趣的可以了解下2023-11-11Go?Excelize?API源碼閱讀Close及NewSheet方法示例解析
這篇文章主要為大家介紹了Go?Excelize?API源碼閱讀Close及NewSheet方法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08