在很多的API接口中,我们发现函数或方法的第一个参数往往是context.Context。Context在进程通信之间提供了取消、超时以及父子进程之间传递数据的方法。那我们在编码实践中是应该将Context存储于struct中还是以参数的方式在函数或方法直接传递呢?
我们的建议是:context.Context不应该被存储在定义的结构体中,而是应该作为函数的参数进行传递。
首先我们来看看golang语言标准包中针对Context的常用方法:
方法名 | 作用 |
---|---|
withCancel | 父进程控制子进程是否取消 |
withDeadline | 父进程控制子进程的结束时间 |
withTimeout | 父进程控制子进程超时时间 |
withValue | 父进程和子进程之间数据传递 |
通过context包中的几个方法可知,context.Context主要的作用就是用于父子进程之间的控制以及数据传递。因为协程是不可以被进行垃圾回收的,所以Context是在父进程结束后,防止协程泄漏的一种方法。
接下来,我们再来看看如果Context是作为字段属性存储在了struct中,会发生什么。如下例子:
type Worker struct {
ctx context.Context
}
func New(ctx context.Context) *Worker {
return &Worker{ctx: ctx}
}
func (w *Worker) Fetch() (*Worker, error) {
_ = w.ctx
}
func (w *Worker) Process(w *Worker) error {
_ = w.ctx
}
根据以上代码可知,Fetch和Process方法共享同一个ctx,即ctx中设置的超时时间,结束时间,是否结束都将是相同的。这样的缺点就是如果调用者想针对不同的方法设置不同的超时时间、结束时机就变的不可行了。
我们再来看看如果Context作为参数传递,而非存储在struct中,又会怎么样呢?如下例子:
type Worker struct {
}
func New() *Worker {
return &Worker{}
}
func (w *Worker) Fetch(ctx context.Context) (*Worker, error) {
_ = ctx
}
func (w *Worker) Process(ctx context.Context) error {
_ = ctx
}
当作为函数或方法的参数传递时,ctx的作用域只在该函数或方法内有效,Fetch和Process方法的ctx相互独立。用户可以针对每个方法单独设置超时时间、取消子进程、数据传递等操作而互不影响。
总结
context.Context的主要作用就是在父子进程(协程)之间进行超时控制、数据传递。
作为参数传递可以方便使用者针对不同的函数设置不同的超时时间和要传递的参数。
而存储在结构体中,则该结构体的所有方法都会共享该context.Context,适合在定义的struct的生命周期内共享的场景。同时调用者使用时的灵活度会降低。
所以,在使用时的建议是优先作为参数传递。