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"))
}
相关推荐
秋意钟9 分钟前
Spring新版本
java·后端·spring
苏三有春22 分钟前
五分钟学会如何在GitHub上自动化部署个人博客(hugo框架 + stack主题)
git·go·github
小蜗牛慢慢爬行29 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
A小白59081 小时前
Docker部署实践:构建可扩展的AI图像/视频分析平台 (脱敏版)
后端
goTsHgo1 小时前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc
waicsdn_haha1 小时前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
Q_19284999061 小时前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
良许Linux1 小时前
0.96寸OLED显示屏详解
linux·服务器·后端·互联网
求知若饥2 小时前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
左羊2 小时前
【代码备忘录】复杂SQL写法案例(一)
后端