开始
*一般是链路起点,或者调用的起点
*ctx := context.Background()
在不确定 context 该用啥的时候,用 *TODO()
*ctx := context.TODO()
WithValue解析
// 路径:go1.20/src/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}
}
//valueCtx 用于存储 key-value 数据,
// 特点:装饰器模式: 在已有 Context 的基础上附加一个存储 key-value 的功能
// 只能存储一个 key,val
type valueCtx struct {
Context
key, val any
}
//外部通过context.value获取
func (c *valueCtx) Value(key any) any {
if c.key == key {
return c.val
}
return value(c.Context, key)
}
func value(c Context, key any) any {
for {
switch ctx := c.(type) {
case *valueCtx:
//当前自己有,就取
if key == ctx.key {
return ctx.val
}
//定义父亲的对象
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey {
return c
}
c = ctx.Context
case *timerCtx:
if key == &cancelCtxKey {
return ctx.cancelCtx
}
c = ctx.Context
case *emptyCtx:
return nil
default:
//去上一级找数据
return c.Value(key)
}
}
}
//结合以下来理解
func TestContext_Parent(t *testing.T) {
ctx := context.Background()
parent := context.WithValue(ctx, "my-key", "my value")
child := context.WithValue(parent, "my-key", "my new value")
t.Log("parent my-key: ", parent.Value("my-key"))
t.Log("child my-key: ", child.Value("my-key"))
child2, cancel := context.WithTimeout(parent, time.Second)
defer cancel()
//儿子可以拿到父亲的 打印出来的是my value
t.Log("child2 my-key:", child2.Value("my-key"))
child3 := context.WithValue(parent, "new-key", "child3 value")
//这个打印出nil,因为parent没有设置这个key,所以没有
t.Log("parent new-key: ", parent.Value("new-key"))
t.Log("child3 new-key: ", child3.Value("new-key"))
//因为父 context 始终无法拿到子 context 设置的值,所以在逼不得已的时候我们可以在父 context 里面放一个 map,后续都是修改这个 map
parent1 := context.WithValue(ctx, "map", map[string]string{})
child4, cancel := context.WithTimeout(parent1, time.Second)
defer cancel()
//儿子可以拿到父亲的数据
m := child4.Value("map").(map[string]string)
m["key1"] = "value1"
nm := parent1.Value("map").(map[string]string)
t.Log("parent1 key1: ", nm["key1"])
}
context_test.go:72: parent my-key: my value
context_test.go:73: child my-key: my new value
context_test.go:77: child2 my-key: my value
context_test.go:80: parent new-key: <nil>
context_test.go:81: child3 new-key: child3 value
context_test.go:90: parent1 key1: value1
控制
context 包提供了三个控制方法,WithCancel、WithDeadline 和 WithTimeout。
三者用法的比较:
没有过期时间,但是又需要在必要的时候取消,使用 WithCancel
在固定时间点过期,使用 withDeadline
在一段时间后过期,使用 withTimeout
而后便是监听 Done( 返回的 channel,不管是主动调用 cancel() 还是超时,都能从这个channel 里面取出来数据。
后面可以用 Err()方法来判断究竟是哪种情况。
父亲可以控制儿子,但是儿子控制不了父亲,