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"))
}
相关推荐
独断万古他化8 分钟前
【Java 实战项目】多用户网页版聊天室:消息传输模块 —— 基于 WebSocket 实现实时通信
java·spring boot·后端·websocket·ajax·mybatis
舒一笑13 分钟前
🚀 我用一行命令,把 OSS 私有文件变成“可直接下载的公网链接”(很多人不会)
后端
小兔崽子去哪了25 分钟前
Docker 安装 PostgreSQL
数据库·后端·postgresql
野犬寒鸦29 分钟前
Redis热点key问题解析与实战解决方案(附大厂实际方案讲解)
服务器·数据库·redis·后端·缓存·bootstrap
snakeshe10101 小时前
深入理解 Java 注解:从原理到实战
后端
Lucaju1 小时前
吃透 Spring AI Alibaba 多智能体|四大协同模式+完整代码
后端
Nyarlathotep01131 小时前
Redis的对象(5):有序集合对象
redis·后端
Java水解1 小时前
Spring Boot 消息队列与异步处理
spring boot·后端
桦说编程1 小时前
AI 真的让写代码变快了吗?
后端