基本介绍
基本介绍
- 在Go 1.7版本中引入了上下文(context)包,用于在并发编程中管理请求范围的数据、控制生命周期、处理取消信号和超时等。
- context在Go中具有重要的作用,特别是在并发编程和网络编程中,因此context通常会作为各个函数和方法的首个入参。
context源码剖析
Context接口
Context接口
Context是context包中的一个接口类型,该接口提供了对上下文的基本操作和属性的访问方法,其定义如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{
}
Err() error
Value(key any) any
}
Context接口中各方法说明:
- Deadline方法:返回上下文的截止时间。其第一个返回值表示上下文的截止时间,第二个返回值表示上下文是否存在截止时间。
- Done方法:返回一个只读的channel,用于接收上下文的取消信号。当上下文被取消时,该channel将会被关闭,从而通知使用者上下文已经被取消。
- Err方法:返回与上下文关联的错误。当上下文被取消时,返回context.Canceled错误,当上下文到达截止时间时,返回context.DeadlineExceeded错误。
- Value方法:根据指定的键获取上下文中关联的值。如果找到与键相关的值,则返回该值,如果未找到,则返回nil。
说明一下:
- Done方法返回的channel的类型是chan struct{},而空struct中实际无法存储任何数据,因为该channel本就不是用作数据存储的,而是用作传递取消信号的。
- 在context包中,有四个结构体类型实现了Context接口,分别是emptyCtx、cancelCtx、timerCtx和valueCtx。
emptyCtx
emptyCtx
emptyCtx是context包中的一个自定义类型,用于表示一个空的上下文,它实现了Context接口,其定义如下:
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 any) any {
return nil
}
emptyCtx实现的四个方法说明:
- Deadline方法:由于emptyCtx不具有截止时间,因此Deadline方法直接返回time.Time和bool类型的零值(false),表示当前上下文不存在截止时间。
- Done方法:由于emptyCtx永远不会被取消,因此Done方法直接将nil作为只读的channel进行返回,使得该channel的读取方无法从中读取到任何取消信号。
- Err方法:由于Err方法返回上下文被取消的原因,而emptyCtx永远不会被取消,因此Err方法直接返回nil。
- Value方法:由于emptyCtx中不存储任何键值对,因此Value方法直接返回nil。
emptyCtx通常作为默认的顶级上下文使用,表示一个空的上下文,其他上下文类型可以在此基础上添加对应的功能,因此context树的根context一定是emptyCtx。示意图如下:
说明一下: 除了emptyCtx以外,其他context都是在已有context的基础上创建的。
创建emptyCtx
通过context包中的Background函数和TODO函数可以创建emptyCtx,其对应的源码如下:
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
说明一下:
- context包中的Background和TODO函数,返回的都是各自全局复用的emptyCtx类型的实例,它们仅仅在语义上稍有不同。
- Background函数返回的emptyCtx通常作为默认的顶级上下文使用,表示一个空的上下文,其他上下文类型可以在此基础上添加对应的功能。TODO函数返回的emptyCtx通常作为临时占位的上下文使用,表示该上下文后期需要替换为其他的上下文,目前先用emptyCtx进行占位。
cancelCtx
cancelCtx
cancelCtx是context包中的一个结构体类型,用于传播取消信号和管理取消操作,它实现了Context接口,其定义如下:
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value // of 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
cause error // set to non-nil by the first cancel call
}
cancelCtx结构体各字段说明:
- Context:嵌套的Context接口类型的匿名结构体,表示当前cancelCtx所继承的父上下文。
- mu:互斥锁,用于保护cancelCtx结构体中各个字段的并发访问。
- done:原子值,用于存储通知上下文已取消的channel。
- children:子context集合,用于保存当前context的子context。
- err:用于表示上下文被取消的错误原因。
- cause:用于表示上下文被取消的具体原因。
children字段的类型为map[canceler]struct{},这里的map中value的类型为空struct,表示我们只关心children中是否存在某一个key,而并不关心这个key对应的value。而map中key的类型为canceler,这是context包中的一个不可导出的接口类型,用于表示可以被取消的上下文,其定义如下:
type canceler interface {
cancel(removeFromParent bool, err, cause error)
Done() <-chan struct{
}
}
canceler接口中各方法说明:
- cancel方法:用于执行上下文的取消操作,其中removeFromParent参数表示是否将当前上下文从其父上下文中移除,err表示取消的错误原因,cause表示取消的具体原因。
- Done方法:返回一个只读的channel,用于接收上下文的取消信号。当上下文被取消时,该channel将会被关闭,从而通知使用者上下文已经被取消。
说明一下:
- cancelCtx结构体中的children字段在保存当前context的子context时,map中的key没有直接使用Context接口类型,而是使用的canceler接口类型,因为children字段只需要关注上下文的cancel和Done这两个方法。
- 通过定义新的接口,将对象中需要关注的能力暴露出来,同时将无关的细节屏蔽掉。这种做法有助于减少错误风险,体现了编程过程中职责内聚和边界分明的思想,同时提高了代码的可读性和可维护性。
创建cancelCtx
通过context包中的WithCancel函数和WithCancelCause函数可以创建cancelCtx,其对应的源码如下:
type CancelFunc func()
type CancelCauseFunc func(cause error)
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := withCancel(parent)
// 3、返回创建的cancelCtx和对应的取消回调函数
return c, func() {
c.cancel(true, Canceled, nil) }
}
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {
c := withCancel(parent)
// 3、返回创建的cancelCtx和对应的取消回调函数
return c, func(cause error) {
c.cancel(true, Canceled, cause) }
}
func withCancel(parent Context) *cancelCtx {
if parent == nil {
panic("cannot create context from nil parent")
}
// 1、创建cancelCtx实例,并用parent对其Context字段进行初始化
c := newCancelCtx(parent)
// 2、将创建的cancelCtx与parent关联起来
propagateCancel(parent, c)
return c
}
func newCancelCtx(parent Context) *cancelCtx {
return &cancelCtx{
Context: parent} // 初始化cancelCtx的父context字段
}
创建cancelCtx的流程如下:
- 创建一个cancelCtx实例,并用给定的父context对cancelCtx的Context字段进行初始化。
- 调用propagateCancel函数将创建的cancelCtx与其父context关联起来,保证父context被取消时,子context也会被取消。
- 返回创建的cancelCtx,同时返回一个闭包函数,闭包函数内部通过调用cancelCtx的cancel方法执行上下文的取消操作。
说明一下:
- WithCancel函数和WithCancelCause函数的区别在于,WithCancelCause函数返回的闭包函数在调用时支持传入cause,在取消上下文时用于设置cancelCtx的cause字段,而WithCancel函数返回的闭包函数在调用时默认cause为nil。
propagateCancel函数
propagateCancel是context包中的一个函数,用于将父context和子context关联起来,保证父context被取消时,子context也会被取消,实现取消信号的传播。propagateCancel函数对应的源码如下:
func propagateCancel(parent Context, child canceler) {
// 1、如果parent不可取消,则直接返回
done := parent.Done()
if done == nil {
return // parent is never canceled
}
// 2、如果parent已经被取消,则将child也取消后返回
select {
case <-done:
// parent is already canceled
child.cancel(false, parent.Err(), Cause(parent))
return
default:
}
if p, ok := parentCancelCtx(parent); ok