golang Goroutine超時(shí)控制的實(shí)現(xiàn)
1.個(gè)人理解
package main import ( "context" "fmt" "runtime" "time" ) func main() { // 為了方便查看設(shè)置的計(jì)數(shù)器 //go func() { // var o int64 // for { // o++ // fmt.Println(o) // time.Sleep(time.Second) // } //}() // 開(kāi)啟協(xié)程 for i := 0; i < 100; i++ { go func(i int) { // 利用context 設(shè)置超時(shí)上下文 ctx, cancel := context.WithTimeout(context.TODO(), 2*time.Second) // 主動(dòng)退出信號(hào) endDone := make(chan struct{}) // 再次開(kāi)啟子協(xié)程異步處理業(yè)務(wù)邏輯 go func() { select { // 監(jiān)聽(tīng)是否超時(shí) case <-ctx.Done(): fmt.Println("Goroutine timeout") return // 處理業(yè)務(wù)邏輯 default: if i == 1 { time.Sleep(10 * time.Second) } // 此處代碼會(huì)繼續(xù)執(zhí)行 //fmt.Println("代碼邏輯繼續(xù)執(zhí)行") // 主動(dòng)退出 close(endDone) return } }() // 監(jiān)聽(tīng)父協(xié)程狀態(tài) select { // 超時(shí)退出父協(xié)程,這里需要注意此時(shí)如果子協(xié)程已經(jīng)執(zhí)行并超時(shí),子協(xié)程會(huì)繼續(xù)執(zhí)行中直到關(guān)閉,這塊需要關(guān)注下。比如:查看數(shù)據(jù)確定數(shù)據(jù)是否已被修改等。 case <-ctx.Done(): fmt.Println("超時(shí)退出", i) cancel() return // 主動(dòng)關(guān)閉 case <-endDone: fmt.Println("主動(dòng)退出", i) cancel() return } }(i) } //time.Sleep(8 * time.Second) time.Sleep(12 * time.Second) // 查看當(dāng)前還存在多少運(yùn)行中的goroutine fmt.Println("number of goroutines:", runtime.NumGoroutine()) }
2.go-zero實(shí)現(xiàn)方式
package main import ( "context" "fmt" "runtime/debug" "strings" "time" ) var ( // ErrCanceled是取消上下文時(shí)返回的錯(cuò)誤。 ErrCanceled = context.Canceled // ErrTimeout是當(dāng)上下文的截止日期過(guò)去時(shí)返回的錯(cuò)誤。 ErrTimeout = context.DeadlineExceeded ) // DoOption定義了自定義DoWithTimeout調(diào)用的方法。 type DoOption func() context.Context // DoWithTimeout運(yùn)行帶有超時(shí)控制的fn。 func DoWithTimeout(fn func() error, timeout time.Duration, opts ...DoOption) error { parentCtx := context.Background() for _, opt := range opts { parentCtx = opt() } ctx, cancel := context.WithTimeout(parentCtx, timeout) defer cancel() // 創(chuàng)建緩沖區(qū)大小為1的通道以避免goroutine泄漏 done := make(chan error, 1) panicChan := make(chan interface{}, 1) go func() { defer func() { if p := recover(); p != nil { // 附加調(diào)用堆棧以避免在不同的goroutine中丟失 panicChan <- fmt.Sprintf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack()))) } }() done <- fn() }() select { case p := <-panicChan: panic(p) case err := <-done: return err case <-ctx.Done(): return ctx.Err() } } // WithContext使用給定的ctx自定義DoWithTimeout調(diào)用。 func WithContext(ctx context.Context) DoOption { return func() context.Context { return ctx } } func main() { ctx, cancel := context.WithCancel(context.Background()) go func() { fmt.Println(1111) time.Sleep(time.Second * 5) fmt.Println(2222) cancel() }() err := DoWithTimeout(func() error { fmt.Println("aaaa") time.Sleep(10 * time.Second) fmt.Println("bbbb") return nil }, 3*time.Second, WithContext(ctx)) fmt.Println(err) time.Sleep(15 * time.Second) //err := DoWithTimeout(func() error { // fmt.Println(111) // time.Sleep(time.Second * 3) // fmt.Println(222) // return nil //}, time.Second*2) // //fmt.Println(err) //time.Sleep(6 * time.Second) // //fmt.Println("number of goroutines:", runtime.NumGoroutine()) }
package fx import ( "context" "testing" "time" "github.com/stretchr/testify/assert" ) func TestWithPanic(t *testing.T) { assert.Panics(t, func() { _ = DoWithTimeout(func() error { panic("hello") }, time.Millisecond*50) }) } func TestWithTimeout(t *testing.T) { assert.Equal(t, ErrTimeout, DoWithTimeout(func() error { time.Sleep(time.Millisecond * 50) return nil }, time.Millisecond)) } func TestWithoutTimeout(t *testing.T) { assert.Nil(t, DoWithTimeout(func() error { return nil }, time.Millisecond*50)) } func TestWithCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) go func() { time.Sleep(time.Millisecond * 10) cancel() }() err := DoWithTimeout(func() error { time.Sleep(time.Minute) return nil }, time.Second, WithContext(ctx)) assert.Equal(t, ErrCanceled, err) }
參考文獻(xiàn):
https://github.com/zeromicro/go-zero/blob/master/core/fx/timeout.go
一文搞懂 Go 超時(shí)控制_51CTO博客_go 超時(shí)處理
到此這篇關(guān)于golang Goroutine超時(shí)控制的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)go Goroutine超時(shí)控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言學(xué)習(xí)技巧之命名規(guī)范
最近在學(xué)習(xí)go語(yǔ)言,發(fā)現(xiàn)了不少需要整理的知識(shí)點(diǎn),所以整理下分享出來(lái),下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言學(xué)習(xí)技巧之命名規(guī)范的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-12-12gin正確多次讀取http?request?body內(nèi)容實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了gin正確多次讀取http?request?body內(nèi)容實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Golang讀寫(xiě)二進(jìn)制文件方法總結(jié)
使用?Golang?的?encoding/gob?包讀寫(xiě)二進(jìn)制文件非常方便,而且代碼量也非常少,本文就來(lái)通過(guò)兩個(gè)示例帶大家了解一下encoding/gob的具體用法吧2023-05-05Go語(yǔ)言中三個(gè)輸入函數(shù)(scanf,scan,scanln)的區(qū)別解析
本文詳細(xì)介紹了Go語(yǔ)言中三個(gè)輸入函數(shù)Scanf、Scan和Scanln的區(qū)別,包括用法、功能和輸入終止條件等,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-10-10Go語(yǔ)言如何使用分布式鎖解決并發(fā)問(wèn)題
這篇文章主要為大家詳細(xì)介紹了Go 語(yǔ)言生態(tài)中基于 Redis 實(shí)現(xiàn)的分布式鎖庫(kù) redsync,并探討其使用方法和實(shí)現(xiàn)原理,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-03-03