- 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())
- }
- 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