go语言context包


context包是个啥?

Go 语言的 context 包提供了一种在应用程序中跨多个 goroutine 传递请求特定数据、取消操作和管理超时的机制, 它被广泛用于处理并发和异步操作.


context的四个核心API

  • WithCancel : (返回一个可取消的context实例和取消函数)

    WithCancel(parent Context) (ctx Context, cancel CancelFunc)
    

    创建一个可以取消的子 Context。当调用返回的 cancel 函数时,与该 Context 关联的所有子 Context 都会被取消, 如果不调用cancel函数,将会一直阻塞!!

  • WithDeadline : (返回一个可取消的context实例和取消函数)

    WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
    

    创建一个带有截止时间的子 Context。当截止时间过期时,与该 Context 关联的操作将自动取消。可以设置过期时间,让context自动过期,也可以不等待过期时间,手动调用cancel函数取消

  • WithTimeout : (返回一个可取消的context实例和取消函数)

    WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
    

    创建一个具有超时时间的子 Context。在指定的超时时间之后,与该 Context 关联的操作将自动取消。和withDeadLine一样,可以设置过期时间,让context自动过期,也可以不等待过期时间,手动调用cancel函数取消,区别在于函数的传参不同.

  • WithValue : (设置键值对,并且返回一个新的context实例)

    WithValue(parent Context, key interface{}, value interface{}) 
    

    创建一个通过键值对传递数据的子 Context。这些数据可以通过 Value 方法在 Context 树中的任何级别进行访问。

注意 : context实例是不可变的,每一次都是新创建的。

context包常用来干啥

每个 Context 实例都可以传递给 Go 协程,并且可以随着协程的执行传递和取消。通过使用 Context,可以实现以下操作:

  • 跨多个 goroutine 传递请求特定的值,如请求 ID、用户身份验证信息等。
  • 实现请求的超时控制,以避免长时间运行的操作阻塞系统。
  • 在并发操作中进行协调,例如控制同时进行的最大请求数或取消正在进行的请求。

context使用注意

在使用 Context 时,需要遵循以下最佳实践:

  • 将 Context 作为函数的第一个参数传递,并将其命名为 ctx。
  • 使用 context.Background() 作为顶级 Context,它是所有其他 Context 的父级。
  • 当一个操作完成或某些条件达到时,调用 cancelFn() 函数来取消与 Context 关联的所有操作。
  • 在 Goroutine 中使用 select 语句监听 Context 的取消信号,以便及时退出。

四个核心API的基本使用

context.WithValue() 的使用

func TestContext_withValue(t *testing.T) {
	ctx := context.Background() // 一般是链路起点和调用起点,创建根context,常用
	//ctx := context.TODO()	// 在不确定context该用啥的时候,用这个,不常用

	ctx = context.WithValue(ctx, key{}, "i am value") // 社区推荐
	//ctx = context.WithValue(ctx, "key", "value") // 这样也可以...

	val := ctx.Value(key{})
	t.Log("val:", val)

	v2 := ctx.Value("不存在的key")
	// 不确定设置的value是否是string或者其他类型的时候,这里断言一下
	valTwo, ok := v2.(string)
	if !ok {
		t.Log("类型错误")
		return
	}
	t.Log("valTwo:", valTwo)
}

context.WithCancel() 如果不调用cancel函数,将会一直阻塞!!

func TestContext_withCancel(t *testing.T) {
	ctx := context.Background() // 一般是链路起点和调用起点,创建根context,常用
	ctx, cancel := context.WithCancel(ctx)
	// 使用完ctx之后,调用cancel,结束整条调用
	//defer cancel()

	// 1秒之后结束调用
	go func() {
		time.Sleep(time.Second * 1)
		cancel()
	}()

	// 调用其他函数或者协程,传递ctx
	t.Log("start...")
	<-ctx.Done() // 上面不调用 cancel(), 此处将会一直阻塞
	t.Log("end...", ctx.Err())
}

context.WithDeadline() 可以设置过期时间,让context自动过期,也可以不等待过期时间,手动调用cancel函数取消

func TestContext_withDeadline(t *testing.T) {
	ctx := context.Background()
	ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*3)) // 3秒后过期
	// deadLineTime:过期时间,isHasTimeout: 是否设置了过期时间
	deadLineTime, isHasTimeout := ctx.Deadline()
	if isHasTimeout {
		t.Log("设置了过期时间,过期时间为:", deadLineTime)
	}
	//defer cancel()		// 等待3秒过期后,进行关闭

	// 2秒之后结束调用
	go func() {
		time.Sleep(time.Second * 2) // 不等待设置的3秒过期时间,手动结束
		cancel()
	}()

	<-ctx.Done() // 这里不会一直阻塞,因为设置了3秒后过期
	t.Log("end...", ctx.Err())
}

context.WithTimeout() 和withDeadLine一样,可以设置过期时间,让context自动过期,也可以不等待过期时间,手动调用cancel函数取消,区别在于函数的传参不同.

func TestContext_withTimeout(t *testing.T) {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, time.Second*3) // 3秒后过期
	// timeOutTime:过期时间,isHasTimeout: 是否设置了过期时间
	timeOutTime, isHasTimeout := ctx.Deadline()
	if isHasTimeout {
		t.Log("设置了过期时间,过期时间为:", timeOutTime)
	}
	//defer cancel()		// 等待3秒过期后,进行关闭

	// 2秒之后结束调用
	go func() {
		time.Sleep(time.Second * 2) // 不等待设置的3秒过期时间,手动结束
		cancel()
	}()

	<-ctx.Done() // 这里不会一直阻塞,因为设置了3秒后过期
	t.Log("end...", ctx.Err())
}

context的父子关系

控制是从上至下

当父context取消或者超时的时候, 所有由它派生的子context都会取消或超时

查找value是从下至上

当使用key查找value的时候,先看自己有没有,没有的话往祖先上查找

上代码

func TestContext_Parent(t *testing.T) {
	ctx := context.Background()
	parent := context.WithValue(ctx, "key", "parent-value")
	childOne := context.WithValue(parent, "key", "childOne-value")
	// 相同的key子context的如果设置了value,会把父context传下来的值覆盖掉
	t.Log("parent-key:", parent.Value("key"))     // parent-key: parent-value
	t.Log("childOne-key:", childOne.Value("key")) // childOne-key: childOne-value

	childTwo, cancel := context.WithTimeout(parent, time.Second)
	defer cancel()
	// 子context没有设置自己的value,还是沿用父context的value
	t.Log("childTwo-key:", childTwo.Value("key")) // childTwo-key: parent-value

	// 父context无法拿到子context设置的值
	childThree := context.WithValue(parent, "new-key", "childThree-value")
	t.Log("parent-new-key:", parent.Value("new-key"))         // parent-new-key: <nil>: <nil>, 父context拿不到childThree设置的 new-key
	t.Log("childThree-new-key:", childThree.Value("new-key")) // childThree-new-key: childThree-value

	// 如果父context一定要拿到子context中设置的值,可以传一个map下去,子孙们往里面装数据,这样能拿到
	parentTwo := context.WithValue(ctx, "parentTwo-key", map[string]string{})
	ptChildOne, cancel := context.WithTimeout(parentTwo, time.Second)
	defer cancel()
	// 拿到context中的map
	m := ptChildOne.Value("parentTwo-key").(map[string]string)
	// 在子context中给map中设置一个数据
	m["key1"] = "value1"
	// 从父context中取出这个map
	ptm := parentTwo.Value("parentTwo-key")
	// 如果不确定类型,先断言一下
	v, ok := ptm.(map[string]string)
	if !ok {
		t.Log("类型错误")
		return
	}
	// 打印 parentTwo-key:  map[key1:value1], 这样的话父context就可以拿到子context中设置的数据
	t.Log("parentTwo-key: ", v)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值