参考: Go Concurrency Patterns: Context - The Go Programming Language
本文将简单介绍下context pkg
内的关键类型与函数, 随后通过两个用例的方式展示context
在并发编程中的使用场景;
缘由
在Server
端一次请求的发生, 往往需要启动新的goroutine
, 例如用来请求数据库或RPC
. 这就提出了一些合理的需求: 有时需要对goroutine
进行生命周期的控制, 例如超过了预期时间则视为请求失败, 应当销毁相关goroutine
; 有时需要传递请求相关的内容, 例如权限信息, Trace-ID
等.
基于此Google
提出了context
包, 在并发场景下实现请求生命周期控制;
定义
Context
是用来在诸多请求相关的goroutine
间传递信息的工具, 一般用于goroutine
生命周期管理; 其可以:
- 支持手动触发取消
cancel
信号: 信号会传播给所有的derived context
; - 支持设置超时时间: 超时后, 完成信号自动传播给所有
derived context
; - 支持传递
request-scope
值: 传递Trace-ID, user-ID
等请求相关的数据;
1 | // Package context defines the Context type, which carries deadlines, |
Context Type
是context
包的核心类型, 结合源码, 可以有更好的理解:
Context
接口定义:1
2
3
4
5
6
7type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}Deadline()
: 返回当前context
的终止时间; 如果ok == false
, 标识没有设置终止时间;Done()
: 返回一个channel
用于标识当前context
是否是终止状态, 终止状态的设置有三种:cancel
: 调用WithCancel(ctx)
获得的cancel
函数, 会传播cancel signal
到所有derived context
中;deadline
: 调用WithDeadline(ctx, time.Time)
, 会在时间到达deadline
后, 自动传播deadline signal
到所有derived context
中;timeout
: 调用WithTimeout(ctx, time.Time)
会在时间到达timeout
后, 自动传播deadline signal
到所有derived context
中;
一般对于并发编程中, 结合
select
语句使用更佳, 示例:1
2
3
4
5
6
7
8
9
10
11
12
13// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }Error
: 一般在ctx
终止后, 用于传递错误信息;Value
: 用于传递request-scope
值;
Context
关键方法:Backgroud() Context
: 返回一个空Context
, 用于根context
用处;WithCancel(parent Context) (ctx Context, cancel CancelFunc)
: 接收一个parent context
返回一个child context
与cancel
函数; 调用cancel
函数就可以将child context
与其衍生context
发送取消信号;WithDeadline(parent Context, d time.Time) (Context, CancelFunc):
接收一个parent context
与超时时间(绝对时间)d time.Time
, 返回一个child context
与cancel
函数; 用户可以手动调用cancel
发送取消信号, 或者超时后child context
会自动发送超时信号;WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
: 接收一个parent context
与时间间隔(相对时间)timeout time.Duration
, 返回一个child context
与cancel
函数; 用户可以手动调用cancel
发送取消信号, 或者超时后child context
会自动发送超时信号;WithValue(parent Context, key, val any) Context
: 会将key value
添加到parent context
后返回一个新的context
, 用于传递请求相关的数据;
使用
Demo
1 | package main |
这里给出了一段示例代码, 其通过BackGroud
方法获得了root ctx
, 随后分别使用了WithTimeout, WithDeadLine
设置了两个带超时信号的child ctx
; 通过go
关键字启动了两个协程, 协程内分别通过select
语句监控ctx
终止信号, 并输出打印信息; 通过运行可以发现, 在2 s
钟后两个goroutine
均输出, 并终止;
Example
本部分将展示pipeline
并发编程模式下, 通过context
发送取消信号, 从而保证在主业务退出后, 所有goroutine
正常销毁;
1 | package cancel |
在上面的示例代码中, 主业务逻辑TestPipeline
, 通过WithCancel
获得了用于发送取消信号的ctx
, 随后通过defer cancel()
保证在业务逻辑退出后, 向所有相关协程发送取消信号, 防止资源泄露;
结论
context
是用于控制请求生命周期的工具, 其天然适配并发编程. 在实际编码中, 形成了约定俗称的签名限制: 函数第一个入参均为ctx context.Context
, 使得跨API
的访问中, 交互更加方便;