golang Goroutine超时控制的实现
作者:一个搬砖的程序猿
日常开发中我们大概率会遇到超时控制的场景,比如一个批量耗时任务、网络请求等,本文主要介绍了golang Goroutine超时控制的实现,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
1.个人理解
package main import ( "context" "fmt" "runtime" "time" ) func main() { // 为了方便查看设置的计数器 //go func() { // var o int64 // for { // o++ // fmt.Println(o) // time.Sleep(time.Second) // } //}() // 开启协程 for i := 0; i < 100; i++ { go func(i int) { // 利用context 设置超时上下文 ctx, cancel := context.WithTimeout(context.TODO(), 2*time.Second) // 主动退出信号 endDone := make(chan struct{}) // 再次开启子协程异步处理业务逻辑 go func() { select { // 监听是否超时 case <-ctx.Done(): fmt.Println("Goroutine timeout") return // 处理业务逻辑 default: if i == 1 { time.Sleep(10 * time.Second) } // 此处代码会继续执行 //fmt.Println("代码逻辑继续执行") // 主动退出 close(endDone) return } }() // 监听父协程状态 select { // 超时退出父协程,这里需要注意此时如果子协程已经执行并超时,子协程会继续执行中直到关闭,这块需要关注下。比如:查看数据确定数据是否已被修改等。 case <-ctx.Done(): fmt.Println("超时退出", i) cancel() return // 主动关闭 case <-endDone: fmt.Println("主动退出", i) cancel() return } }(i) } //time.Sleep(8 * time.Second) time.Sleep(12 * time.Second) // 查看当前还存在多少运行中的goroutine fmt.Println("number of goroutines:", runtime.NumGoroutine()) }
2.go-zero实现方式
package main import ( "context" "fmt" "runtime/debug" "strings" "time" ) var ( // ErrCanceled是取消上下文时返回的错误。 ErrCanceled = context.Canceled // ErrTimeout是当上下文的截止日期过去时返回的错误。 ErrTimeout = context.DeadlineExceeded ) // DoOption定义了自定义DoWithTimeout调用的方法。 type DoOption func() context.Context // DoWithTimeout运行带有超时控制的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() // 创建缓冲区大小为1的通道以避免goroutine泄漏 done := make(chan error, 1) panicChan := make(chan interface{}, 1) go func() { defer func() { if p := recover(); p != nil { // 附加调用堆栈以避免在不同的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调用。 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) }
参考文献:
https://github.com/zeromicro/go-zero/blob/master/core/fx/timeout.go
到此这篇关于golang Goroutine超时控制的实现的文章就介绍到这了,更多相关go Goroutine超时控制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!