译文 context.Context 是作为参数传递还是存储在 struct 中?

yudotyang · 2021年03月01日 · 56 次阅读

在很多的 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 的生命周期内共享的场景。同时调用者使用时的灵活度会降低。

所以,在使用时的建议是优先作为参数传递。

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册