背景

Context 已经成为 golang 并发编程中 如何控制超时的标准解决方案了。网上很多 context 的用法例子,但大多数都不够精短。为了能用最少的代码说清楚 context 的精髓用法,特意写了这个帖子。

场景1 主动取消go routine 的执行

有些场景中,我们需要手动地结束 go rouinte 运行。比如某个 go routine 一直在等待某个资源,但这个资源永远都不会就绪,这时候需要提前结束这个 go routine。

 
package main

import (
   "context"
   "fmt"
   "time"
)

func watch(ctx context.Context, contexName string)  {
   go func() {
      for {
         select {
         case <-ctx.Done():
            fmt.Println(contexName, " stop ")
            return
         default:
            time.Sleep(100*time.Millisecond)// 每100ms监测一次
         }
      }

   }()
}

func main() {

   rootCtx:=context.Background()

   ctx1,_ :=context.WithDeadline(rootCtx, time.Now().Add(time.Second))// 1秒后结束运行,到达某个时间点结束
   watch(ctx1,"WithDealine context")

   ctx2,_:=context.WithTimeout(rootCtx, 2*time.Second)// 只运行2秒, 从开始运行算起
   watch(ctx2,"WithTimeout context")

   ctx3,cancel:= context.WithCancel(rootCtx)// 可以手动结束的 context
   watch(ctx3,"WithCancel context")

   time.Sleep(3*time.Second)
   cancel()//3秒后手动结束运行

   time.Sleep(time.Second)
}
 
场景2 链路传值
package main

import (
   "context"
   "fmt"
)

func A(ctx context.Context)  {
   fmt.Println("This is function A")
   ctxB:=context.WithValue(ctx,"k1","v1")
   B(ctxB)
}

func B(ctx context.Context)  {
   fmt.Println("This is function B")
   ctxC:=context.WithValue(ctx,"k2","v2")
   C(ctxC)
}

func C(ctx context.Context)  {
   fmt.Println("This is function C")
   fmt.Println(ctx.Value("k1"))
   fmt.Println(ctx.Value("k2"))
}

func main() {
   rootCtx:=context.Background()

   A(rootCtx)
}
总结一下
  1. Context 一共有6种 Background, Todo, WithDeadline, WithTimeout, WithCanel, WithValue。前两种本质上都是 一个空的 empty 用作 context 的 "种子", 中间三种 用于 终止代码的运行, 最后一种用于传值
  2. Context的核心用法是主动终止代码的运行,次要用法才是链路传值。
  3. Done() 方法会返回一个 可读的channel, 若通过 select case :<- contex.Done() 读到了说明这个 context应该结束了,通常要 break 或者 return。 特别的是, 这个 channel 是不带 buffer的, select语句记得写 default, 否则死锁报错。

参考资料

juejin.cn/post/684490… juejin.cn/post/709610…