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文章来源:https://www.toymoban.com/news/detail-704757.html
一文搞懂 Go 超时控制_51CTO博客_go 超时处理文章来源地址https://www.toymoban.com/news/detail-704757.html
到了这里,关于golang Goroutine超时控制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!