Context(上下文)一般理解为程序单元的一个运行状态、现场、快照。其中上下是指存在上下层的传递,上会把内容传递给下,程序单元则指的是 Goroutine。即:在一组goroutine之间传递取消信号、共享的值、超时时间等等。
context 的函数、接口、结构体
Context 接口定义如下:
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
其中:
- Deadline 方法返回当前 Context 被取消的时间,也就是完成工作的截止时间(deadline);
- Done 方法返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个Channel;
- Err 方法会返回当前 Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值:
- 如果当前 Context 被取消就会返回 Canceled 错误;
- 如果当前 Context 超时就会返回 DeadlineExceeded 错误;
- Value 方法会从 Context 中返回键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法仅用于传递跨 API 和进程间跟请求域的数据。
连续多次调用同一个方法,得到的结果都是相同的。
常用方法:context.Backgroud()、context.TODO()
func Background() Context {
return background
}
func TODO() Context {
return todo
}
context.Background
是上下文的默认值(根Context),所有其他的上下文都应该从它衍生出来;context.TODO
应该仅在不确定应该使用哪种上下文时使用;
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
其中:
context.WithCancel
函数能够从context.Context
中衍生出一个新的子上下文并返回用于取消该上下文的函数。一旦我们执行返回的取消函数,当前上下文以及它的子上下文都会被取消,所有的 Goroutine 都会同步收到这一取消信号。context.WithDeadline
会返回父上下文的副本,并将 deadline 调整为不迟于 d。当时间超过了截止日期后会调用context.timerCtx.cancel
同步取消信号,从而实现定时取消。context.WithTimeout
也能通过创建可以被取消的计时器上下文context.timerCtx
,实现定时取消context.WithValue
能从父上下文中创建一个子上下文,用于传递goroutine之间需要共享的数据
官方的几点Context使用建议:
- 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。
- 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。
- 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
- 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。
- Go 语言中的
context.Context
的主要作用还是在多个 Goroutine 组成的树中同步取消信号以减少对资源的消耗和占用。建议尽量不要传值:
——在一个处理过程中,有若干子函数、子协程。各种不同的地方会向 context 里塞入各种不同的 k-v 对,最后在某个地方使用。你根本就不知道什么时候什么地方传了什么值,以及这些值会不会被“覆盖”。