Blog
Blog copied to clipboard
Context 包解析
Context 用来管理 goroutine 的上下文,包括传递请求作用域的值、退出信号和处理一个请求时涉及的所有 goroutine 的截止时间等。
代码解析
// Context 的方法可以由多个协程同时调用
type Context interface {
// Deadline 返回代表这个 context 的工作取消的时间
// 当没有设置截止时间时,ok==false
// 连续调用 Deadline 时只有第一次生效,后面都返回同样的结果
Deadline() (deadline time.Time, ok bool)
// Done 返回一个关闭的 channel,代表这个 context 的工作应该被取消。如果这个 context 永远不能取消则应该返回 nil。连续调用时只有第一次生效,后面都返回同样的结果
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
// 当 context 退出时 Context.Err 返回的错误
var Canceled = errors.New("context canceled")
// 当过了 context 的截止时间后,Context.Err 返回的错误
var DeadlineExceeded error = deadlineExceededError{}
type deadlineExceededError struct{}
// 没有接收器名称的方法,这里用的是非指针的 deadlineExceededError。为什么???
func (deadlineExceededError) Error() string { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true }
// 永远不退出,没有值,没有截止时间。它不是结构体,因此这个类型的变量必须有确定的地址
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
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
var (
background = new(emptyCtx) // 全局唯一的地址
todo = new(emptyCtx)
)
// 实际是 emptyCtx,通常用于 main 函数、初始化和测试
func Background() Context {
return background
}
// 也是 emptyCtx,当不清楚用那种 context 或还没有可用的 context 时使用
func TODO() Context {
return todo
}
// 定义一个函数类型,调用时立即停止某项操作,只在第一次调用时生效,后续调用什么都不做
type CancelFunc func()
// 复制一份 parent context,此副本作为 parent 的 child 并遵循其退出行为。
// 当调用 cancel 函数时或 parent 的 Done channel 关闭时,ctx 的 Done channel 将关闭,无论哪一个先发生。
// 返回 context 的副本和一个用来将此副本从 parent 的 children 中移除并退出的函数
// 应当在运行在此 context 中的工作完成时执行此 CancelFunc,context 退出时将释放与之相关的资源
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) } // 返回的还是地址,interface 可以是指针
}
// 返回一个初始化的 cancelCtx
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
// 判断 parent 是否退出,其 child 也遵循 parent 的退出行为
func propagateCancel(parent Context, child canceler) {
if parent.Done() == nil {
return // 表示 parent 实际是 emptyCtx,永不退出
}
if p, ok := parentCancelCtx(parent); ok { // 判断 parent 是不是 cancelCtx 类型
p.mu.Lock()
if p.err != nil {
// p.err 有值,说明 parent 已经退出,child 也将退出,但不从其 parent 的 children 中移除
child.cancel(false, p.err)
} else {
// 如果 parent 没有退出且还没有 child,则将此 child 作为其 child
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{} // 值不重要,只是一个空 struct
}
p.mu.Unlock()
} else {
// 如果 parent 没有退出,启动协程等待 parent 和 child 退出
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
// 循环检测 parent 的实际类型,直到发现是 *cancelCtx 时返回其指针和 true
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx: // 和上面 WithCancel 一样,这里也是指针
return c, true
case *timerCtx:
return &c.cancelCtx, true
case *valueCtx:
parent = c.Context // 继续循环
default:
return nil, false // 上面的 case 都没有匹配到时,说明是 background 或 TODO类型,直接返回
}
}
}
// 从 parent 中移除 child
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent) // 判断 parent 类型
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
// 是 context 类型,可以直接退出。*cancelCtx 和 *timerCtx 是其具体实现
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
// 可重复使用的关闭的通道
var closedchan = make(chan struct{})
func init() {
close(closedchan) // 一开始就关闭 closedchan
}
// cancelCtx 可以退出,当退出时,它也取消其任何实现了 canceler 的 children
type cancelCtx struct {
Context
mu sync.Mutex // 保护以下字段
done chan struct{} // created lazily, 通过第一个取消调用来关闭
children map[canceler]struct{} // 通过第一个取消调用将其设置为 nil
err error // 通过第一个取消调用将其设置为非 nil
}
func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
}
func (c *cancelCtx) String() string {
return fmt.Sprintf("%v.WithCancel", c.Context)
}
// 这个函数关闭 c.done,退出其每个 child,如果 removeFromParent 是 true,
// 则将 c 从其 parent 的 children 中移除
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // 说明已经退出过,直接返回
}
c.err = err
// 如果没有 done,则将关闭的 channel 赋值给它,如果有则关闭
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
// 将 c 的 children 都退出,注意这里的实参 false 和上面的 removeFromParent 没有关系,
// 因为 removeFromParent 是用来决定是否将 c 从其 parent 的 children 中移除
for child := range c.children {
// 当持有 parent 的锁时也就获取了 child 的锁,可以直接操作
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}
// 返回 parent 的一个副本,其截止时间是 deadline。如果 parent 的截止时间比 deadline 早,
// 则新 context 在语义上等同于 parent。当时间过了 deadline、cancel 函数被调用、parent 的
// Done channel 关闭时,新 context 的 Done channel 将被关闭,无论哪种情况先发生。
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
// 只有 timerCtx 的 Deadline 返回 true,如果 parent 的截止时间早于 deadline,
// 直接返回带有 cancel 函数的 context
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: deadline,
}
propagateCancel(parent, c) // 遵循 parent 的退出行为
d := time.Until(deadline)
if d <= 0 {
// 已经过了 deadline,将 c 退出并从其 parent 的 children 中移除
c.cancel(true, DeadlineExceeded)
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
// 设置退出时间,超时后 c 将从其 parent 的 children 中移除,可在此时间之前撤销此操作
c.timer = time.AfterFunc(d, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
// 它通过停止 timer 来实现退出操作,然后将退出操作委托给 cancelCtx.cancel。
// It implements cancel by stopping its timer then delegating to cancelCtx.cancel.
type timerCtx struct {
cancelCtx // timerCtx 具有 cancelCtx 的全部字段和方法
timer *time.Timer // 从属于 cancelCtx.mu
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) String() string {
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline))
}
func (c *timerCtx) cancel(removeFromParent bool, err error) {
// c 的 parent 退出后,parent 的 child 也会退出
c.cancelCtx.cancel(false, err)
if removeFromParent {
// 将 c 从其 parent 的 children 中移除
removeChild(c.cancelCtx.Context, c)
}
// 如果有计时器,停止计时器
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
// 返回带有键值对的 parent 的副本。
// context 值仅用于传输处理和 API 的请求域,而不是用于给函数传递可选参数。
// 提供的 key 必须是可比较的,并且不能是 string 等内置类型,以避免包与 context 之间的冲突。
// 使用此函数的用户应当为 key 定义用户自己的类型。
// 当分配给一个 interface{} 时,要避免重新分配内存,context 的 key 通常使用具体类型 struct{}。
// 或者,对外暴露的 context key 变量的静态类型应该是一个指针或 interface。
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}
}
// valueCtx 带有一个键值对。它实现了该键的值,并且将所有其它调用都委托给嵌套的 Context。
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) String() string {
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}