在实际应用场景中常常有对协程跟踪的需求,比如我们的业务代码中会启动大量goroutine,而在实际中常常出现调用请求终止,因此开启的大量goroutine我们需要关闭这些goroutine来保证我们的计算机没有因此浪费内存和计算资源;Golang语言提供了context包,其主要用于控制goroutine的生命周期和在不同的goroutine间传递信息,传递的信息可以为请求范围的值、截止日期、取消信号,我们可以通过context跟踪开启的goroutine,并向它们下达终止命令。
context接口
首先我们来看golang中对context的定义,如下所示;context在golang中被定义为接口,接口包含了如下四种方法;首先是Done()方法,其返回类型为一个时间和布尔类型,布尔类型返回值表明是否设置了截止时间,时间为设置的截止时间;其次是Done()方法,其返回类型为struct类型的只读chan,多次调用Done()方法返回的是同一个chan,这个chan有值可读的时候说明收到了终止信号,协程因此可以做退出处理;Err()方法会返回一个error说明为什么取消context,当Done得到的chan被关闭时才会返回非空值;Value(key any)该context绑定的一个值。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
context创建与使用
func main() {
var wg sync.WaitGroup = sync.WaitGroup{}
wg.Add(1)
var ctx context.Context = context.Background() //创建一个空context
ctx1, ok := context.WithTimeout(ctx, 2*time.Second)//创建一个定时可取消context
defer ok()
go func() {
defer wg.Done()
var ctx11 context.Context = context.WithoutCancel(ctx1)//创建一个可取消context
go func() {
for {
select {
case <-ctx11.Done():
fmt.Println("groutine1-1 is cancel", time.Now())
return
default:
fmt.Println("goroutine1-1 is running", time.Now())
}
}
}()
for {
select {
case <-ctx1.Done()://收到退出信号 退出
fmt.Println("groutine1 is cancel", time.Now())
return
default:
fmt.Println("goroutine1 is running", time.Now())
}
}
}()
wg.Wait()
}
空context
context在golang中是以树的形式存在,树的根context为一个空context,我们可以调用context包提供的Background()方法或TODO()方法创建一个根context,两者方法没有太大的区别,context树上的节点context退出时,其子context也会退出。
func Background() Context {
return backgroundCtx{}
}
可取消的context
我们调用WithCancel方法,向其传入一个父context,方法会给我们一个绑定上的子context,父context取消退出时,该子context也会退出取消。
func WithoutCancel(parent Context) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
return withoutCancelCtx{parent}
}
可定时取消的context
WithTimeout方法与WithCancel方法传入的参数相比,多了一个时间参数,返回的context不仅跟随父context,当我们传入的时间长度到了后该context也会取消退出。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
Goang语言的context包还提供了WithDeadline方法,该方法与WithTimeout方法类似,但区别是传入的时间不是时间间隔而是具体截止时间,到了该时间或收到父context的取消时间,该context均会取消退出。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
return WithDeadlineCause(parent, d, nil)
}
携带key-value键值对的Context
与前面方法得到的context不同,WithValue方法返回的context不提供退出机制,其主要作用是携带键值对信息用于父子协程间信息的传递,如果要加上退出机制常常与可取消的context或可定时取消的context结合使用。
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}