通过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的主要业务逻辑,包含异步操作。
【通过golang context实现请求超时控制和goroutine生命周期控制】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
此方法与上方法原理上相同。

    推荐阅读