likes
comments
collection
share

Go进阶:一文掌握Go中Context上下文的使用

作者站长头像
站长
· 阅读数 27

Go Version 1.18

Context是Go语言在1.7版本之后加入的一个标准库的接口,其定义如下:

A Context carries a deadline, a cancellation signal, and other values across
API boundaries.
Context's methods may be called by multiple goroutines simultaneously.

Context跨越API边界传递超时、取消信号和其他值

Context核心方法

type Context interface {
  
   Deadline() (deadline time.Time, ok bool)
   
   Done() <-chan struct{}

   Err() error

   Value(key any) any
}
  • 定义了四个方法:
    • Deadline():设置context.Context被取消的时间,也就是截止时间
    • Done():返回一个只读的Channel,当Context被取消或者到达截止时间的话,这个Channel就会被关闭,表示这个Context链路结束了
    • Value(key any) any:从context.Context当中获取键对应的值,类似于Map当中的get方法,如果没有对应的key就会返回nil,键值对是通过WithValue的方法进行传入
    • Err():返回context.Context结束的原因,只会在Done()返回的Channel被关闭时才会是非空,也就是此时的Context链接结束的时候返回非空,如果尚未被取消返回nil,返回值有两种情况:
      • 如果是被取消的话,返回Canceled
      • 如果是超时的话,返回DeadlineExceeded
// If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error explaining why:
// Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed.
// After Err returns a non-nil error, successive calls to Err return the same error.
Err() error

Context创建以及应用场景

根Context的创建

主要是两种方式创建方式:

  • context.Backgroud()
  • context.TODO()

都是用于创建根context,根context是一个空的context,不具备任何的功能。但是在一般情况下,如果当前函数没有上下文入参,都会使用context.Backgroud创建一个根context作为起始的上下文传递

context创建

根context在创建之后不具备任何的功能,为了让context发挥作用,使用With系列的派生函数进行派生

context.WithCancel

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

函数接受一个父Context,返回一个子Context还有一个取消函数,当取消函数被调用的时候,子Context会被取消,同时向子Context关联的Done()发送信号,并且它的子上下文也会被取消

context.WithCancel应用场景

func main() {
   ctx, cancel := context.WithCancel(context.Background())

   go Watch(ctx, "1")
   go Watch(ctx, "2")
   time.Sleep(5 * time.Second)
   cancel()
   time.Sleep(5 * time.Second)
}
func Watch(ctx context.Context, s string) {
   for {
      select {
      case <-ctx.Done():
         fmt.Println("done")
         return
      default:
         fmt.Println(s)
      }
   }
}
2
2
2
2
1
1
1
1
1
done
2
done

context.WithDeadline

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 

context.WithDeadline也是一个取消控制函数,方法有两个参数,第一个参数是Context,第二个参数是截止时间,同样会返回一个子context和一个取消函数CancelFunc

使用的时候,没有到截止时间,可以通过手动调用CancelFunc来取消子Context,控制子Goroutine的退出,如果到了截止时间,都没有调用CancelFunc,子context的Done()管道也会收到一个取消信号,用来控制子Goroutine退出

func main() {
   ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(4*time.Second))

   defer cancel()
   go Watch(ctx, "1")
   go Watch(ctx, "2")

   time.Sleep(7 * time.Second)
   fmt.Println("end")
}
func Watch(ctx context.Context, s string) {
   for {
      select {
      case <-ctx.Done():
         fmt.Println("done")
         return
      default:
         fmt.Println(s)
      }
   }
}
1
1
1
1
2
2
2
2
done
1
done
end

在调用cancel()之前已经超时,ctx.Done()信号退出

context.WithTimeout

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

context.WithTimeout和context.WithDeadline的作用类似,但是略有不同,在第二个传递的参数上,context.WithTimeout传递的是时间长度,而context.WithDeadline传递的是具体时间

func main() {
   ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)

   defer cancel()
   go Watch(ctx, "1")
   go Watch(ctx, "2")

   time.Sleep(7 * time.Second)
   fmt.Println("end")
}
func Watch(ctx context.Context, s string) {
   for {
      select {
      case <-ctx.Done():
         fmt.Println("done")
         return
      default:
         fmt.Println(s)
      }
   }
}

context.WithValue

func WithValue(parent Context, key, val any) Context 

context.WithValue函数从父context中创建一个子context用于传值,函数参数是父context中创建一个子context用于传值,参数为父context,key,val键值对

项目中使用一般用于上下文信息的传递,比如请求唯一id,以及trace_id等,用于链路追踪以及配置透传

func main() {
   ctx := context.WithValue(context.Background(), "name", "lisi")
   go Get(ctx)
   time.Sleep(7 * time.Second)
   fmt.Println("end")
}
func Get(ctx context.Context) {
   fmt.Printf("name is %v", ctx.Value("name"))
}
转载自:https://juejin.cn/post/7286632584150155325
评论
请登录