在goroutine调用树中传递通知或者数据。context.Context可在多个goroutine之间进行传递,相比于channel,它规定了接口,更加方便使用,规范性更强。
退出通知:通知可以传递给goroutine调用树上的每个goroutine,使得在多级goroutine调用链上取消动作更加容易,可以方便地取消调用链上的任何一个goroutine。
需要注意的是,还是需要我们自己手动处理Done()的返回值来决定是否退出goroutine,context只是负责传递信息,但是停止goroutine的行为还是要自己处理。
传递数据:数据可以传递给goroutine调用树上的每个goroutine
context.Context接口的全部方法体现了其作用:传递通知和传递数据,4个接口方法定义如下:
// 传递退出通知
// 当Context被取消,Done()函数返回的是一个已经关闭的channel,从而可以使得多个goroutine得到通知。无论是主动调用CancelFunc还是Context设定的定时时间到期,都会通知退出。
func Done() ←chan struct{}
// 传递数据
// 通过key-value的形式传递数据,一个Context只能传递一对key-value
func Value(any) any
// 获取Context退出的原因
func Err() error
// 获取Context是否设置了最后期限,如果设置了,则bool返回true,并且返回最后期限的时间;否则返回bool为false
func Deadline() (time.Time, bool)
func Background() Context
func TODO() Context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val any) Context
一个Context可以作为其它Context的父Context,只需要在使用API创建Context的时候指定。
// parent context
// ctx_parent -> ctx_level1 -> ctx_level2
ctx_parent, cancel := context.WithCancel(context.Background())
// level1 context
// 6s后过期
ctx_level1, _ := context.WithTimeout(ctx_parent, time.Second * 6)
// level2 context
// level1过期的话,level2也会跟着过期
ctx_level2, _ := context.WithCancel(ctx_level1)
var wg sync.WaitGroup
f := func(ctx context.Context, level int, interval int) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Printf("level-%d received done\n", level)
return
default:
fmt.Printf("level-%d running\n", level)
time.Sleep(time.Duration(interval) * time.Second)
}
}
}
wg.Add(2)
go f(ctx_parent, 0, 1)
go f(ctx_level1, 1, 2)
go f(ctx_level2, 2, 1)
wg.Wait()
wg.Add(1)
cancel()
wg.Wait()
通知退出
父Context
的退出通知会向下传递到子Context
但是子Context
的退出通知则不会向上传递到父Context
值传递
子Context
可以找到父Context
中的key-value
但是父Context
找不到子Context
中的key-value
,示例如下
root := context.Background()
ctx_parent := context.WithValue(root, keyType("parent"), "parent-value")
ctx_child := context.WithValue(ctx_parent, keyType("child"), "child-value")
var wg sync.WaitGroup
type keyType string
// WithValue需要使用自定义的key类型,防止和使用context的库造成冲突
f := func(ctx context.Context, name string, keyname keyType) {
defer wg.Done()
fmt.Printf("[goroutine %s] ctx[%v]=%v\n", name, keyname, ctx.Value(keyname))
}
wg.Add(2)
go f(ctx_parent, "goroutine-1", keyType("child")) // 拿不到 key为child的值
go f(ctx_child, "goroutine-2", keyType("parent")) // 可以拿到key为parent的值
wg.Wait()
Go标准库contex文档
context.Context
作为一个struct的成员,而是应该通过函数传参的方式传递,通常Context都放在第一个参数,名字通常命名为ctxnil
的Context,应该要用context.TODO()
代替WithDeadline(parent context.Context, d time.Time)
,如果parent也设置有deadline,并且parent的deadline已经比d要早了,那么WithDeadline返回的Context的deadline时间以parent为准CancelFunc
类型,这个函数类型是With系列函数的第二个返回值(除WithValue),可以直接调用这个函数,用来广播退出通知。CancelFunc
是并发安全的,如果同时调用的话,首次调用后的后续调用不影响。(A CancelFunc may be called by multiple goroutines simultaneously. After the first call, subsequent calls to a CancelFunc do nothing.)