golang Goroutine超时控制的实现

 更新时间:2023年09月14日 10:10:26   作者:一个搬砖的程序猿  
日常开发中我们大概率会遇到超时控制的场景,比如一个批量耗时任务、网络请求等,本文主要介绍了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

 一文搞懂 Go 超时控制_51CTO博客_go 超时处理

到此这篇关于golang Goroutine超时控制的实现的文章就介绍到这了,更多相关go Goroutine超时控制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言学习技巧之命名规范

    Go语言学习技巧之命名规范

    最近在学习go语言,发现了不少需要整理的知识点,所以整理下分享出来,下面这篇文章主要给大家介绍了关于Go语言学习技巧之命名规范的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-12-12
  • Windows下安装VScode 并使用及中文配置方法

    Windows下安装VScode 并使用及中文配置方法

    这篇文章主要介绍了Windows下安装VScode 并使用及中文配置的方法详解,本文通过图文并茂的形式给大家介绍,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • gin正确多次读取http request body内容实现详解

    gin正确多次读取http request body内容实现详解

    这篇文章主要为大家介绍了gin正确多次读取http request body内容实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • Golang读写二进制文件方法总结

    Golang读写二进制文件方法总结

    使用 Golang 的 encoding/gob 包读写二进制文件非常方便,而且代码量也非常少,本文就来通过两个示例带大家了解一下encoding/gob的具体用法吧
    2023-05-05
  • 详解MongoDB Go Driver如何记录日志

    详解MongoDB Go Driver如何记录日志

    这篇文章主要为大家介绍了MongoDB Go Driver如何记录日志详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Go语言错误处理异常捕获+异常抛出

    Go语言错误处理异常捕获+异常抛出

    这篇文章主要介绍了Go语言错误处理异常捕获和异常抛出,Go语言的作者认为java等语言的错误处理底层实现较为复杂,就实现了函数可以返回错误类型以及简单的异常捕获,虽然简单但是也非常精妙,大大的提高了运行效率,下文需要的朋友可以参考一下
    2022-02-02
  • golang如何判断文件是否存在

    golang如何判断文件是否存在

    判断一个文件是否存在是一个相当常见的需求,在golang中也有多种方案实现这一功能,下面就跟随小编一起学习一下具体的实现方法吧
    2024-11-11
  • Go1.21新增slices包中函数的用法详解

    Go1.21新增slices包中函数的用法详解

    Go 1.21新增的 slices 包提供了很多和切片相关的函数,可以用于任何类型的切片,本文为大家整理了部分函数的具体用法,感兴趣的小伙伴可以了解一下
    2023-08-08
  • Go语言中三个输入函数(scanf,scan,scanln)的区别解析

    Go语言中三个输入函数(scanf,scan,scanln)的区别解析

    本文详细介绍了Go语言中三个输入函数Scanf、Scan和Scanln的区别,包括用法、功能和输入终止条件等,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-10-10
  • Go语言如何使用分布式锁解决并发问题

    Go语言如何使用分布式锁解决并发问题

    这篇文章主要为大家详细介绍了Go 语言生态中基于 Redis 实现的分布式锁库 redsync,并探讨其使用方法和实现原理,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-03-03

最新评论