01
介绍
在 Go1.7 中,标准库加入了 context 包,context 包定义了一个 Context (上下文)类型,可以在 Api 之间和进程之间传递信息,还提供了超时(timeout)和取消(cancel)机制。
Go 标准库中,database/sql,net,net/http 等包中都使用了 Context。
在 Go 应用开发中,一般用于请求链路中传递上下文信息,控制子 goroutine 等场景中。
02
Context 接口
var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }
context 包定义了一个 Context 接口,包含 4 个方法:
- Deadline() (deadline time.Time, ok bool)
Deadline 方法返回结果有两个,第一个是截止时间,到了这个截止时间,Context 会自动取消;第二个是一个 bool 类型的值,如果 Context 没有设置截止时间,第二个返回结果是 false,如果需要取消这个 Context,就需要调用取消函数。 - Done() <-chan struct{}
Done 方法返回一个只读的 channel 对象,类型是 struct{},在 goroutine 中,如果 Done 方法返回的结果可以被读取,代表父 Context 调用了取消函数。 - Err() error
Err 方法返回 Context 被取消的原因。 - Value(key interface{}) interface{}
Value 方法返回此 Context 绑定的值。它是一个 kv 键值对,通过 key 获取对应 value 的值。
03
创建 Context
context 包中包含两个生成顶层 Context 的方法:
- func Background() Context
Background 方法一般用于 main 函数,init 函数,测试和创建根 Context 的时候。 - func TODO() Context
TODO 方法,当不清楚使用哪个上下文时,可以使用 TODO 方法。
这两个方法都是返回一个非 nil,空 Context,没有任何值,不会被 cancel,不会超时,没有截止日期。实际上,这两个方法的底层实现是一样的。
var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }
这两个方法都是 emptyCtx 的指针类型,是一个不可取消,没有设置截止时间,没有任何值得 Context。
type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (*emptyCtx) Done() <-chan struct{} { return nil } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil } func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
04
CancelFunc 类型
type CancelFunc func()
CancelFunc 用于主动让 goroutine 停止。多个goroutine 可以同时调用 CancelFunc。在第一个调用之后,随后对 CancelFunc 的调用什么也不做。
05
函数
创建了顶层 Context,想要创建子 Context,可以使用以下方法:
- WithCancel
WithCancel 方法,基于父 Context,接收一个父 Context 参数,生成一个新的子 Context,和一个 cancel 函数,用于取消 Context。 - WithDeadline
WithDeadline 方法,基于父 Context,接收一个父 Context 参数,和一个截止时间的参数,生成一个新的子 Context,和一个 cancel 函数,可以使用 cancel 函数取消 Context,也可以等到截止时间,自动取消 Context。 - WithTimeout
WithTimeout 方法,基于父 Context,接收一个父 Context 参数,和一个超时时间的参数,生成一个新的子 Context,和一个 cancel 函数,可以使用 cancel 函数取消 Context,也可以等到超时时间,自动取消 Context。 - WithValue
WithValue 方法,基于父 Context,生成一个新的子 Context,携带了一个 kv 键值对,一般用于传递上下文信息。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context
示例代码:
var name string func main () { // ctx, cancle := context.WithCancel(context.Background()) // d := time.Now().Add(time.Second * 2) // ctx, cancle := context.WithDeadline(context.Background(), d) ctx, cancle := context.WithTimeout(context.Background(), time.Second * 2) valueCtx := context.WithValue(ctx, name, "lucy") go work(valueCtx) time.Sleep(time.Second * 3) fmt.Println("停止工作。") cancle() time.Sleep(time.Second * 5) } func work(ctx context.Context) { for { select { case <- ctx.Done(): fmt.Println(ctx.Value(name), "工作结束!") return default: fmt.Println(ctx.Value(name), "工作中。") time.Sleep(time.Second * 1) } } }
06
规范
- 不要将上下文存储在结构类型中;而是将上下文以参数形式传递给需要它的函数。并且 Context 应该是第一个参数,该参数通常命名为 ctx。
- 即使函数允许,也不要传递nil Context 的参数。如果不确定使用哪个上下文,就使用 context.TODO。
- 可以将相同的 Context 上下文传递给在不同 goroutine 中运行的函数。上下文是线程安全的,可由多个 goroutine 同时使用。
07
总结
Context 一般用于控制 goroutine,使用 Context 主动取消 goroutine 的运行。除此之外,关于控制 goroutine,我们还可以使用 WaitGroup 和 channel 被动取消 goroutine。