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"))
}
相关推荐
专注VB编程开发20年5 分钟前
asp.net mvc如何简化控制器逻辑
后端·asp.net·mvc
用户67570498850235 分钟前
告别数据库瓶颈!用这个技巧让你的程序跑得飞快!
后端
千|寻1 小时前
【画江湖】langchain4j - Java1.8下spring boot集成ollama调用本地大模型之问道系列(第一问)
java·spring boot·后端·langchain
程序员岳焱1 小时前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
后端·sql·mysql
龚思凯1 小时前
Node.js 模块导入语法变革全解析
后端·node.js
天行健的回响1 小时前
枚举在实际开发中的使用小Tips
后端
wuhunyu1 小时前
基于 langchain4j 的简易 RAG
后端
techzhi1 小时前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端
写bug写bug2 小时前
手把手教你使用JConsole
java·后端·程序员