通过golang context实现请求超时控制和goroutine生命周期控制

原文链接:https://blog.csdn.net/qq180782842/article/details/88942602

通过golang context实现请求超时控制和goroutine生命周期控制

下例中:lazyHander负责请求参数处理和context的初始化:

客户端发送超时参数,采用WithTimeout来控制context在指定时间内关闭其管道,若所有子进程中均声明了正确的context信号接收处理,则lazyHandler派生的所有子进程会返回并被回收。

若客户端不发送超时参数,则采用WithCancel函数来控制所有子线程的生命周期。若所有子进程中均声明了正确的context信号接收处理,一旦lazyHandler的defer cancel语句被执行,则由lazyHandler派生的所有子进程会返回并被回收。

func lazyHandler(resp http.ResponseWriter, req *http.Request) {
var (
ctx context.Context
cancel context.CancelFunc
)
query, err := url.ParseQuery(req.URL.RawQuery)
if err != nil{
log.Fatalln(“Http error”)
}
keys, ok := query[“timeout”]
if !ok {
ctx, cancel = context.WithCancel(context.Background())
} else {
timeout, err := time.ParseDuration(keys[0])
if err != nil {
ctx, cancel = context.WithCancel(context.Background())
} else {
ctx, cancel = context.WithTimeout(context.Background(), timeout)
}
}
defer cancel()
s := funcA(ctx)
resp.Write([]byte(s))
}

funcA是handler的主要业务逻辑,包含异步操作。

func funcA(ctx context.Context) string{
c := make(chan error, 1)
go func() {
c <- funcB(ctx, func()error {
time.Sleep(time.Duration(5) * time.Second)
return nil
})
}()
select {
case <-ctx.Done():
err:= <-c
return ctx.Err().Error() +"; " + err.Error()
case <-c:
return “Lazy reply after 5 sec”
}
}

这里注意,虽然ctx.Done()信号已被接收,并不意味这该函数能马上返回,因为若该函数涉及到子线程funcB的调用,则需要等待子线程返回,否则子线程会失去控制且可能引起内存泄露。

func funcB(ctx context.Context, f func() error) error {
c := make(chan error ,1)
go func(){
c <- f()
}()
select {
case <-ctx.Done():
return errors.New(“Interrupt by context”)
case err:= <-c:
return err
}
}

子线程funcB同样能接收parent context的ctx.Done(),能在timeout指定时间内强制返回,或正常执行到程序段结束。另外,lazyHandler的defer cancel()也能确保funcB总能结束。

若funcA中涉及到服务之间的调用,即调用某api的endpoint, 也可以将context存于request中,利用request接口来实现请求超时。

func funcA(ctx context.Context) string {
c := make(chan string, 1)
go func() {
c <- func(ctx context.Context) string{
req, err := http.NewRequest(“GET”, “http://localhost:8079/”, nil)
if err != nil {
return err.Error()
}
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err.Error()
}else{
data, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
return string(data)
}
}(ctx)
}()
select {
case <-ctx.Done():
err := <-c
return ctx.Err().Error() + "; " + err
case str:= <-c:
return str
}
}

注意, 这里使用了req = req.WithContext(ctx), 使得context传到req对象中,实现类似funcB中的子线程控制。

通过中间件Middleware传入带有timeout的context, 参见
开发者可以更优雅地通过中间件的形式设置timeout, 另外必须在handler实现中使用select监听ctx.Done()信号, 或将该ctx交由支持ctx作为参数的接口方法处理, 如:

rpcResponse, err := grpcFuncFoo(ctx, …)
1
此方法与上方法原理上相同。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值