Golang context包详解

900 阅读3分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

Context 是 Golang 官方的一个 package,它定义了 Context 接口类型,包含Done/Err/Deadline 方法以及字段 value。

type Context interface {
   // 返回context的超时时间
   Deadline() (deadline time.Time, ok bool)

   // 在Context被取消或超时时返回一个close的channel,close的channel可以作为广播通知,告诉给context相关的函数要停止当前工作然后返回
   // 当一个父operation启动一个goroutine用于子operation,这些子operation不能够取消父operation。下面描述的WithCancel函数提供一种方式可以取消新创建的Context
   // Context可以安全的被多个goroutine使用。开发者可以把一个Context传递给任意多个goroutine然后cancel这个context的时候就能够通知到所有的goroutine
   Done() <-chan struct{}

   // If Done is not yet closed, Err returns nil.
   // If Done is closed, Err returns a non-nil error explaining why:
   // Canceled if the context was canceled
   // or DeadlineExceeded if the context's deadline passed.
   // After Err returns a non-nil error, successive calls to Err return the same error.
   Err() error

   // 返回Context相关的数据 
   Value(key interface{}) interface{}
}

在Golang server中,每个 request 都是在单个 goroutine 中完成的,并且在单个 goroutine 中也会有请求其他服务(启动一个goroutine)的场景,这就会涉及多个 goroutine的调用。

image.png

如何有效控制这些 goroutine 成了一个问题(主要是退出通知和元数据传递问题),Google的解决办法是 Context 机制,相互调用的 goroutine 之间通过传递 context 保持关联。

image.png

这样一来,通过传递 context 就可以追踪 goroutine 调用树,并在这些调用树之间传递通知和元数据。

虽然 goroutine 是平行的,没有父子关系,但 context 设计成包含父子关系的,这样可以描述 goroutine 之间调用的树形结构。

创建 Context

通过 BackGound 创建根节点 context,不能被cancel。该 context 通常由 request 的第一个 goroutine 创建,它不能被取消、没有值、也没有过期时间。

func Background() Context {
   return background
}

在根节点之后,是创建子节点,为了很好的控制子节点,Context 包提供的方法均带有第二返回值——CancelFunc类型。

// 带cancel返回值的Context,一旦cancel被调用,即取消该创建的context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 

// 带有效期cancel返回值的Context,即必须到达指定时间点调用的cancel方法才会被执行
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) 

// 带超时时间cancel返回值的Context,类似Deadline,前者是时间点,后者为时间间隔
// 相当于WithDeadline(parent, time.Now().Add(timeout)).
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

父节点Context可以主动通过调用cancel方法取消子节点Context,而子节点Context只能被动等待。同时父节点Context自身一旦被取消(如其上级节点Cancel),其下的所有子节点Context均会自动被取消。

配合 Context 提供的 Done 方法,子 goroutine 可以检查自身是否被父节点 cancel:

select { 
    case <-ctx.Done(): 
        // do some clean… 
}