永远记得cancel()
godoc中有这样一段代码在context.WithTimeout之上:
func slowOperationWithTimeout(ctx context.Context) (Result, error) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() // releases resources if slowOperation completes before timeout elapses
return slowOperation(ctx)
}
当slowOperation在3s内完成了, 如果不defer cancel那么context中使用的资源将都在3s超时之后才会被释放.
所以养成好的习惯, 使用defer cancel()去及时释放资源.
Grpc 中 context 会被 cancel.
在设计合理的系统中, ctx的cancel流程应该是这样的:
ctx, cancel := context.WithCancel()
// 当此函数退出时, ctx就应该被销毁, 所以一定得调用cancel让ctx暂用的资源释放以便GC.
defer cancel()
doSomething(ctx)
在Grpc中调用服务端接口就是这样设计的: 当客户端请求完服务器, 就会cancel这个ctx.
下面是个例子
// 客户端
c := pb.NewUserClient(conn)
rsp, err := c.GetUser(context.Background(), &pb.GetUserParams{
UserId: 1,
})
// 服务端
func (*User) GetUser(ctx context.Context, p *GetUserParams) (rsp *pb.User, err error) {
doSomeThing(ctx)
}
在以上代码中, 如果在doSomeThing函数中使用到了ctx, 并且使用了异步调用就得注意了
func doSomathing(ctx context.Context) (err error){
a:=1;
go func() {
doOtherThing(ctx)
}
return
}
现在doOtherThing这个方法不会被正确执行, 因为当客户端调用完GetUser之后就会cancel掉这个ctx, 而不会等待后台协程.
而如果我们确实需要在后台执行doOtherThing怎么办呢? 那就不要使用这个ctx作为入参了, 而应该使用context.Backgroud().
cancel代码好像在
google.golang.org/grpc/stream.go:145
里, 我也没太花时间追了, 不保证正确, 有兴趣的朋友可以再去看源码研究.
context.TODO() 和 context.Backgroud() 的区别
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
return todo
}
大致的意思是如果在调用一个函数时, 发现他需要一个ctx, 但是你还不确定传入什么ctx(待开发), 或者就想传递一个nil, 这时候就应该使用 TODO()
在一个不需要取消的环境中, 如一个长驻运行的http服务, 就应该使用Background()
作为给子方法的入参.
网友评论