细数 Context 使用场景

本文详细解读Go语言Context的实现原理,并介绍其在请求链路传值、超时取消、资源管理及goroutine泄露防治中的实际应用。通过实例演示了如何利用context进行主动取消、超时控制,以及避免协程泄露,是理解Go上下文管理的关键指南。
摘要由CSDN通过智能技术生成

点击上方蓝色“Golang来啦”关注我哟

加个“星标”,天天 15 分钟,掌握 Go 语言

你好,我是四哥。

前一篇文章从源码的角度详细介绍了 Context 的实现原理,但是还没有提到 Context 的使用场景,今天我们一起来看下:

1.请求链路传值。

传值使用方式如下:

func func1(ctx context.Context) {
 ctx = context.WithValue(ctx, "k1", "v1")
 func2(ctx)
}

func func2(ctx context.Context) {
 fmt.Println("func2:",ctx.Value("k1").(string))
 ctx = context.WithValue(ctx, "k2", "v2")
 func3(ctx)
}

func func3(ctx context.Context) {
 fmt.Println("func3:",ctx.Value("k1").(string))
 fmt.Println("func3:",ctx.Value("k2").(string))
}

func main() {
 ctx := context.Background()
 func1(ctx)
}

我们在 func1() 通过函数 WithValue() 设置了一个键值对 k1-v1,在 func2() 可以获取到 func1() 设置的键值对,如果调用 func3() 时把这个 ctx 继续传入的话,在 func3() 中依然还是可以获取到 k1-v1。

但是在 func1() 中获取不到 func2() 设置的键值对 k2-v2,因为 context 只能自上而下携带值,这点需要注意。

2.取消耗时操作,及时释放资源。

使用 channel + select 的机制:

func func1() error {
 respC := make(chan int)        // 起消息通知作用
 // 处理逻辑
 go func() {
  time.Sleep(time.Second * 3)   // 模拟处理业务逻辑
  respC <- 1
  close(respC)
 }()

 // 判断是否超时
 select {
 case r := <-respC:
  fmt.Printf("Resp: %d\n", r)
  return nil
 case <-time.After(time.Second * 2):     // 超过设置的时间就报错
  fmt.Println("catch timeout")
  return errors.New("timeout")
 }
}

func main() {
 err := func1()
 fmt.Printf("func1 error: %v\n", err)
}

上面的方式平时也会用到,通过 context 怎么实现呢?

下面来看下如何使用 context 进行主动取消、超时取消。

主动取消:

func func1(ctx context.Context, wg *sync.WaitGroup) error {
 defer wg.Done()
 respC := make(chan int)
 go func() {
  time.Sleep(time.Second * 5)    // 模拟业务逻辑处理
  respC <- 10
 }()
 // 取消机制
 select {
 case <-ctx.Done():
  fmt.Println("cancel")
  return errors.New("cancel")
 case r := <-respC:
  fmt.Println(r)
  return nil
 }
}

func main() {
 wg := &sync.WaitGroup{}
 ctx, cancel := context.WithCancel(context.Background())
 wg.Add(1)
 go func1(ctx, wg)
 time.Sleep(time.Second * 2)
 cancel()   // 主动取消
 wg.Wait()  // 等待 goroutine 退出
}

超时取消:

func func1(ctx context.Context) {
 resp := make(chan int)
 go func() {
  time.Sleep(time.Second * 5)     // 模拟处理逻辑
  resp <- 1
 }()
 // 超时机制
 select {
 case <-ctx.Done():
  fmt.Println("ctx timeout")
  fmt.Println(ctx.Err())
 case <-resp:
  fmt.Println("done")
 }
 return
}
func main() {
 ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
 defer cancel()
 func1(ctx)
}

3.防止 goroutine 泄露。

引自【深度解密 Go 语言之 context[1]

func gen() <-chan int {
 ch := make(chan int)
 go func() {
  var n int
  for {
   ch <- n
   n++
   time.Sleep(time.Second)
  }
 }()
 return ch
}

这是一个可以生成无限整数的协程,但如果我只需要它产生的前 5 个数,那么就会发生 goroutine 泄漏:

func main() {
 for n := range gen() {
  fmt.Println(n)
  if n == 5 {
   break
  }
 }
 // ……
}

当 n == 5 的时候,直接 break 掉。那么 gen 函数的协程就会执行无限循环,永远不会停下来。发生了 goroutine 泄漏。

用 context 改进这个例子:

func gen(ctx context.Context) <-chan int {
 ch := make(chan int)
 go func() {
  var n int
  for {
   select {
   case <-ctx.Done():
    return
   case ch <- n:
    n++
    time.Sleep(time.Second)
   }
  }
 }()
 return ch
}

func main() {
 ctx, cancel := context.WithCancel(context.Background())
 defer cancel() // 避免其他地方忘记 cancel,且重复调用不影响

 for n := range gen(ctx) {
  fmt.Println(n)
  if n == 5 {
   cancel()
   break
  }
 }
 // ……
}

增加一个 context,在 break 前调用 cancel 函数,取消 goroutine。gen 函数在接收到取消信号后,直接退出,系统回收资源。

总结

这篇文章列出的几个例子是 context 最基本的使用场景,其他框架、第三包基本上都是从这几种用法扩展的,所以非常有必要掌握基础用法。

另外希望这篇文章能给你带来帮助,如果文中有理解错误之处或者你还想到其他用法,可以在留言区留言,一定回复!抱团学习不孤单!

参考资料

[1]

深度解密Go语言之context: https://qcrao.com/2019/06/12/dive-into-go-context/

推荐阅读:

哇,14662 字解析 Go 语言 Context(附图)

资料下载

点击下方卡片关注公众号,发送特定关键字获取对应精品资料!

  • 回复「电子书」,获取入门、进阶 Go 语言必看书籍。

  • 回复「视频」,获取价值 5000 大洋的视频资料,内含实战项目(不外传)!

  • 回复「路线」,获取最新版 Go 知识图谱及学习、成长路线图。

  • 回复「面试题」,获取四哥精编的 Go 语言面试题,含解析。

  • 回复「后台」,获取后台开发必看 10 本书籍。

对了,看完文章,记得点击下方的卡片。关注我哦~ 👇👇👇

以上分享如果对你有所帮助,就给四哥点个赞,分享给身边学习 Go 语言的朋友叭,这样四哥也有更新下去的动力,跪谢各位父老乡亲嘞!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Seekload

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值