context详解
context详解
简介
context.Context是go的标准库,主要用于跨goroutine传递取消信号、截止时间(超时)、以及在请求生命周期内传递数据。可以用来设置截止日期、同步信号、传递请求相关值的结构体,与goroutine有比较密切的关系。
在web程序中,每个request都需要开启一个goroutine做一些事情,这些goroutine有可能会开启其他的goroutine去访问后端资源,比如数据库、rpc服务器等,他们需要访问一些共享的资源,比如用户身份信息、认证token、请求截止时间等,这时候可以通过Context,来跟踪这些goroutine,并且通过Context来控制它们,这就是go提供的Context,称为上下文。
context主要是用来做并发控制的。
- 程序中使用方式
在服务入口创建context上下文,并不断透传下去。
context被当做第一个参数(官网建议),并且不断透传下去。
Context定义
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface()
}
Deadline
Deadline方法是获取设置的截止时间的意思,第一个返回值是截止时间,到了这个时间点,Context会自动发起取消请求,第二个返回值ok===false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。
Done
Done方法返回一个只读的chan,类型为struct{},在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起取消请求,通过Done方法收到这个信号后,就应该清理操作,然后退出goroutine,之后,Err方法会返回一个错误,告知为什么Context被取消。
Err
Err方法返回取消的错误原因。为什么Context被取消。
Value
Value方法获取该Context上绑定的值,是一个键值对,通过一个Key才可以获取对应的值,这个值一般是线程安全的。
创建context
context包主要提供两种方式创建context:
- context.Backgroud()
- 上下文的默认值,其他所有的上下文都应该从它衍生(Derived)出来。
- context.TODO()
- 只在不确定应该使用哪种上下文时使用。
大多数情况下,我们都使用context.Backgroud作为起始的上下文向下传递。上述两种方式创建的是根context,不具备任何功能。
具体功能使用context包提供的With系列函数进行派生:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
WithValue携带数据
创建携带treace_id的ctx,在程序中传递,从中派生的任何context都会获取此值。
注意事项:
不建议使用context传递关键参数,关键参数应该显示声明出来,不应该隐式处理,context最好是携带签名、treace_id这类值。
超时控制
通常健壮的程序都要设置超时间,避免因为服务端长时间响应消耗资源,所以一些web框架或rpc框架都会采用withTimeout或withDeadline来做超时控制,当一次请求到达我们设置的超时时间,就会及时取消,不再往下执行。
withTimeout或withDeadline都会返回一个cancelFunc方法,通过调用这个方法可以提前进行取消。
WithCancel取消控制
日常业务开发中,我们往往为了完成一个复杂的需求会开多个goroutine去完成一些工作,这就可能导致我们在一次请求中开了多个goroutine却无法控制它们,这时我们就可以使用withCancel来衍生一个context传递到不同的goroutine中,当我们想让这些goroutine停止运行,就可以调用cancel来进行取消。
总结
go语言中的context.Context的主要作用还是在多个goroutine组成的树中同步取消信号以减少对资源的消耗和占用。
虽然它有传值的功能,但是这个功能很少用到,在真正使用传值的功能也应该非常谨慎,使用context.Context进行传参请求是一种非常差的设计。
比较常见的使用场景是:传递请求对应用户的认证令牌;分布式追踪的请求ID。
浙公网安备 33010602011771号