context.go
首先给出了 Context
接口,有四个方法,从这里也可以看出 context 这个平时称作上下文的东西是干嘛用的。注释太多了,本来想全删掉,但下不了手,有一说一这注释实在太棒了,甚至想翻译一遍。
// src/context/context.go ---- line 58
// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
// The close of the Done channel may happen asynchronously,
// after the cancel function returns.
//
// WithCancel arranges for Done to be closed when cancel is called;
// WithDeadline arranges for Done to be closed when the deadline
// expires; WithTimeout arranges for Done to be closed when the timeout
// elapses.
//
// Done is provided for use in select statements:
//
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See https://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancellation.
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
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.WithValue and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
//
// Packages that define a Context key should provide type-safe accessors
// for the values stored using that key:
//
// // Package user defines a User type that's stored in Contexts.
// package user
//
// import "context"
//
// // User is the type of value stored in the Contexts.
// type User struct {...}
//
// // key is an unexported type for keys defined in this package.
// // This prevents collisions with keys defined in other packages.
// type key int
//
// // userKey is the key for user.User values in Contexts. It is
// // unexported; clients use user.NewContext and user.FromContext
// // instead of using this key directly.
// var userKey key
//
// // NewContext returns a new Context that carries value u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext returns the User value stored in ctx, if any.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
Value(key interface{}) interface{}
}
当然这里只是给了接口的定义,没有实际落地的结构体,于是标准库给出了几个具体实现,分别是 emptyCtx
, cancelCtx
, timerCtx
, valueCtx
共四种,其中 timerCtx
是继承自 cancelCtx
的。当然作用也都很明了,直接看名称和注释就知道。
// src/context/context.go ---- line 169
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int
// src/context/context.go ---- line 339
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
// src/context/context.go ---- line 452
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
// src/context/context.go ---- line 523
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val interface{}
}
可以看出 context 都是一层包一层的,像洋葱一样。每个结构体内都有一个 Context
接口的 parent 。所以如果结构体自己不重载(不该用这个词,但就这个意思嘛)接口中那四个方法的话,调用的就是 parent 的方法。
只有 emptyCtx
自己有那四个方法,实现了 Context
接口,所以我们经常都用 context.Background()
来作为最内部的 Context
然后再在外面一层一层地套(context.Background()
返回的就是一个 *emptyCtx
,当然也可以自定义一个类型来实现 Context
接口用作最内层)。套一层 cancelCtx
那就可以 cancel 了,套一层 timerCtx
就可以有时间限制,时间到自动 cancel(注意,因为 timerCtx
是继承的 cancelCtx
噢,所以其实直接只要 timerCtx
也可以),再套 valueCtx
就可以存值了,要注意的是 valueCtx
存值是通过结构体字段,所以每对 k, v 都需要包一层 valueCtx
来存储。
以前一直以为 context 存值是通过哈希表,看了源码才知道是一层一对键值对,找不到就往上抛,让上层找。但这样的效率应该和顺序遍历一样吧,甚至不如,因为还有函数栈开销,但这种设计真的太棒了。
当然上一段是我第一时间的反应,没多加思考,仔细想想其实真正该用 context 存的值并不多,所以现在这样应该是最好的解决方案。简单高效且优雅。
关于 context 用处,在写 Golang 程序的时候,很容易就会起很多 goroutine,而开发者不是真的有精力去关心每个 goroutine 的状态,如果不注意很容易造成 goroutine 泄露,就像内存泄漏一样,非常难受,消耗资源不做事。而 context 就是用来优雅管理 goroutine 的。其实 Context
接口的注释真的说得很清楚了,还是找个时间翻译一下吧
example_test.go
这里有一些简单的 context 用例。都是很简单的。