简介
在使用 Go 语言进行网络编程时,经常需要使用 context 来传递跟每个 Request 相关的信息,因此它是一个非常重要的结构。
在 Go 1.7 中 context 已经进入标准库 context,直接 import "context" 就可以使用。
这篇文章主要通过解析标准库提供的 context.go 源码来掌握 context 的使用方法。
Context 源码解析
从源码中可知,Context 是定义为一个接口类型 interface{}
。
// A Context carries a deadline, a cancelation signal, and other values across API boundaries.
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
标准库用了一个 emptyCtx 来作为默认的 implementer
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
...
由于默认的 emptyCtx 是未导出的,因此,标准库又提供了两个公共的函数 Background 和 TODO 来访问这个 implementer。
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
此外,还提供了一个 CancelFunc 的回调函数原型,WithCancel() 函数可用来给 Context 注册一个 cancel 函数。
还提供了另外几个其他的 With 函数,分别是 WithDeadline(), WithTimeout() 和 WithValue()。
WithDeadline 和 WithTimeout 都是给 parent Context 加上一个 timeout 并返回一个 cancel func。
WithValue() 复制原 context,并把一对 key、value 保存到副本中。
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflect.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
由于 Context 本身是 interface{} 接口类型,没法保存 key/value,其实质是通过一个包的私有类型 valueCtx 来保存的,如下:
// 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{}
}
可见,通过传入一个 parent context,以及 key/value,最终会保存在 valueCtx 中并返回。
http.Request 中对 Context 的使用
下面通过对 http.Request 的分析来学习 Context 的常见使用方式。
http.Request 是一个结构体类型,其有一个成员就是 ctx Context 接口类型(私有成员)。通过包方法 http.NewRequest() 来创建。创建时默认 Context 成员未初始化,也就是 nil。
request 对象提供了一个公共方法 Context() 来访问私有的 ctx 成员,如下:
func (r *Request) Context() context.Context {
if r.ctx != nil {
return r.ctx
}
return context.Background()
}
可见,默认创建好的 request, 其 ctx 是 Background() 。
Request 结构体提供了 WithContext(ctx context.Context) 方法用来给自己的 ctx 成员赋值。
// WithContext returns a shallow copy of r with its context changed
// to ctx. The provided ctx must be non-nil.
func (r *Request) WithContext(ctx context.Context) *Request {
if ctx == nil {
panic("nil context")
}
r2 := new(Request)
*r2 = *r
r2.ctx = ctx
return r2
}
网友评论