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)
// }
//}()
// 開啟協(xié)程
for i := 0; i < 100; i++ {
go func(i int) {
// 利用context 設(shè)置超時(shí)上下文
ctx, cancel := context.WithTimeout(context.TODO(), 2*time.Second)
// 主動退出信號
endDone := make(chan struct{})
// 再次開啟子協(xié)程異步處理業(yè)務(wù)邏輯
go func() {
select {
// 監(jiān)聽是否超時(shí)
case <-ctx.Done():
fmt.Println("Goroutine timeout")
return
// 處理業(yè)務(wù)邏輯
default:
if i == 1 {
time.Sleep(10 * time.Second)
}
// 此處代碼會繼續(xù)執(zhí)行
//fmt.Println("代碼邏輯繼續(xù)執(zhí)行")
// 主動退出
close(endDone)
return
}
}()
// 監(jiān)聽父協(xié)程狀態(tài)
select {
// 超時(shí)退出父協(xié)程,這里需要注意此時(shí)如果子協(xié)程已經(jīng)執(zhí)行并超時(shí),子協(xié)程會繼續(xù)執(zhí)行中直到關(guān)閉,這塊需要關(guān)注下。比如:查看數(shù)據(jù)確定數(shù)據(jù)是否已被修改等。
case <-ctx.Done():
fmt.Println("超時(shí)退出", i)
cancel()
return
// 主動關(guān)閉
case <-endDone:
fmt.Println("主動退出", 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í)返回的錯誤。
ErrCanceled = context.Canceled
// ErrTimeout是當(dāng)上下文的截止日期過去時(shí)返回的錯誤。
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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go Sentinel 動態(tài)數(shù)據(jù)源配置指南(示例詳解)
本文介紹了如何使用Go語言配置Sentinel的動態(tài)數(shù)據(jù)源,并通過本地文件和Nacos兩種方式實(shí)現(xiàn)動態(tài)配置,通過這種方式,可以靈活地管理和更新限流規(guī)則,提升系統(tǒng)的穩(wěn)定性和響應(yīng)速度,感興趣的朋友跟隨小編一起看看吧2025-01-01
使用gin框架搭建簡易服務(wù)的實(shí)現(xiàn)方法
go語言web框架挺多的,本文就介紹了一下如何使用gin框架搭建簡易服務(wù)的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
源碼剖析Golang中map擴(kuò)容底層的實(shí)現(xiàn)
之前的文章詳細(xì)介紹過Go切片和map的基本使用,以及切片的擴(kuò)容機(jī)制。本文針對map的擴(kuò)容,會從源碼的角度全面的剖析一下map擴(kuò)容的底層實(shí)現(xiàn),需要的可以參考一下2023-03-03
Golang?單元測試和基準(zhǔn)測試實(shí)例詳解
這篇文章主要為大家介紹了Golang?單元測試和基準(zhǔn)測試實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Go語言實(shí)現(xiàn)圖片快遞信息識別的簡易方法
這篇文章主要為大家介紹了Go語言實(shí)現(xiàn)圖片快遞信息識別的簡易方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10

