6.1 Context
context.Context接口定义了四个方法:
Deadline: 返回context.Context被取消的时间,即完成工作的截止时间Done:返回一个 Channel, 会在当前工作完成或者上下文被取消后关闭,多次调用Done方法会返回同一个 ChannelErr: 返回context.Context结束的原因,只会在Done方法对应的 Channel 关闭时返回非空的值:- 若
context.Context被取消,返回Canceled错误 - 若
context.Context超时,返回DeadlineExceeded错误
- 若
Value:从context.Context中获取键对应的值,对于同一个上下文来说,多次调用Value并传入相同的Key会返回相同的结果,该方法可以用来传递请求特定的数据
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
6.1.1 设计原理
context.Context最大作用是 在 Goroutine 构成的树形结构中对信号进行同步以减少计算资源的浪费。

context.Context 的作用是在不同 Goroutine 之间同步请求特定数据、取消信号以及处理请求的截止日期。
每一个 context.Context 都会从最顶层的 Goroutine 一层一层传递到最下层。context.Context 可以在上层 Goroutine 执行出现错误时,将信号及时同步给下层。

当最上层的 Goroutine 因为某些原因执行失败时,下层的 Goroutine 由于没有接收到这个信号所以会继续工作。
当使用 context.Context 时,就可以在下层及时停掉无用的工作以减少额外资源的消耗:

6.1.2 默认上下文
context.Background、context.TODO都会返回预先初始化好的私有变量 background 和 todo:
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
这两个私有变量都是通过 new(emptyCtx) 语句初始化,是指向私有结构体 context.emptyCtx 的指针:
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
}
context.emptyCtx 通过空方法实现了 context.Context 接口中的所有方法,没有任何功能。

context.Background 和 context.TODO 互为别名,没有太大的差别,只是在使用和语义上不同:
context.Background是上下文的默认值,所有其他的上下文都应该从它衍生出来;context.TODO应该仅在不确定应该使用哪种上下文时使用;
在多数情况下,若当前函数没有上下文作为入参,会使用 context.Background 作为起始的上下文向下传递。
6.1.3 取消信号
WithCancel
context.WithCancel 函数能够从 context.Context 中衍生出一个新的Context并返回用于取消该上下文的函数。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
context.newCancelCtx将传入的上下文包装成私有结构体context.cancelCtxcontext.propagateCancel会构建父子上下文之间的关联,当父上下文被取消时,子上下文也会被取消
func propagateCancel(parent Context, child canceler) {
done := parent.Done()
if done == nil {
return // 父上下文不会触发取消信号
}
select {
case <-done:
child.cancel(false, parent.Err()) // 父上下文已经被取消
return
default:
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
child.cancel(false, p.err)
} else {
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
函数流程如下:
parent.Done() == nil:parent不会触发取消事件时,当前函数会直接返回child的继承链包含可以取消的上下文时,会判断parent是否已经触发了取消信号:- 若已经被取消,
child会立刻被取消 - 若没有被取消,
child会被加入parent的children列表中,等待parent释放取消信号
- 若已经被取消,
- 当父上下文是开发者自定义的类型、实现了
context.Context接口并在Done()方法中返回了非空的管道时:- 运行一个新的 Goroutine 同时监听
parent.Done()和child.Done()两个 Channel - 在
parent.Done()关闭时调用child.cancel取消子上下文
- 运行一个新的 Goroutine 同时监听
context.propagateCancel 在 parent 和 child 之间同步取消和结束的信号,保证在 parent 被取消时,child 也会收到对应的信号。
context.cancelCtx.cancel会关闭上下文中的 Channel 并向所有的子上下文同步取消信号:
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return
}
c.err = err
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
for child := range c.children {
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}
WithDeadline, WithTimeout
context.WithDeadline 和 context.WithTimeout 能创建可以被取消的计时器上下文 context.timerCtx:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // 已经过了截止日期
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
函数流程如下:
- 判断父上下文的截止日期与当前日期
- 使用
time.AfterFunc创建定时器 - 时间超过了截止日期后调用
context.timerCtx.cancel同步取消信号
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
context.timerCtx.cancel会停止context和定时器。
6.1.4 传值
context.WithValue能从父上下文中创建一个子上下文,传值的子上下文使用 context.valueCtx 类型:
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
若 context.valueCtx 中存储的键值对与 context.valueCtx.Value 方法中传入的参数不匹配,就会从父上下文中查找该键对应的值直到某个父上下文中返回 nil 或者查找到对应的值。
