Context基本用法(1)

124 阅读2分钟

开始

*一般是链路起点,或者调用的起点
*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()方法来判断究竟是哪种情况。

父亲可以控制儿子,但是儿子控制不了父亲,