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 {Contextmu sync.Mutex // protects following fieldsdone chan struct{} // created lazily, closed by first cancel callchildren map[canceler]struct{} // set to nil by the first cancel callerr 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 {cancelCtxtimer *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 {Contextkey, 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 用例。都是很简单的。
