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

Go Version 1.18

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

css 复制代码
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核心方法

go 复制代码
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
go 复制代码
// 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

go 复制代码
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 

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

context.WithCancel应用场景

go 复制代码
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)
      }
   }
}
bash 复制代码
2
2
2
2
1
1
1
1
1
done
2
done

context.WithDeadline

go 复制代码
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 

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

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

go 复制代码
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)
      }
   }
}
bash 复制代码
1
1
1
1
2
2
2
2
done
1
done
end

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

context.WithTimeout

go 复制代码
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

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

go 复制代码
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

go 复制代码
func WithValue(parent Context, key, val any) Context 

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

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

go 复制代码
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"))
}
相关推荐
追逐时光者2 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_2 小时前
敏捷开发流程-精简版
前端·后端
苏打水com3 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧4 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧4 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧4 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧4 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧4 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng6 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6016 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring