context.Context
是 Go 语言中管理协程生命周期和传递信息的核心工具,其主要作用如下:
- 超时退出:例如在处理 HTTP 请求时,可以在超时或取消时及时终止对应的协程。
- 传递元信息:通过 Context 的 Value 方法,可以携带与操作相关的元数据(如请求 ID、用户身份等),并在各层级 API 间传递。
- 传递取消信号:通过
Done()
通道获取取消信号来控制协程退出
context.Context
实质上只是一个接口,类型如下:
type Context interface {
// 返回 Context 是否有截止时间,以及截止时间的具体值
Deadline() (deadline time.Time, ok bool)
// 当 Context 被取消或超时时返回一个关闭的 channel
Done() <-chan struct{}
// 返回 Context 被取消的具体原因
Err() error
// 从 Context 中获取一个与 key 相关联的值
Value(key any) any
}
实现该接口的核心类型
context.emptyCtx
type emptyCtx struct{}
- 描述:空的上下文,所有方法都返回默认值,主要用于生成根节点
特性:
- 没有超时
- 没有取消机制
- 不携带任何值
生成方式:
context.Background()
context.TODO()
说明:
- 两者底层都是
context.emptyCtx
的实例,功能没有区别,仅语义不同 Background
通常用于根节点上下文TODO
用于暂时未定的上下文场景
- 两者底层都是
- 使用场景:作为上下文树的根节点,或作为占位符
context.cancelCtx
type cancelCtx struct { Context mu sync.Mutex // protects following fields done atomic.Value // of chan struct{}, created lazily, closed by first cancel call children map[canceler]struct{} // set to nil by the first cancel call err error // set to non-nil by the first cancel call cause error // set to non-nil by the first cancel call }
- 描述:提供取消功能的上下文,通过嵌套父上下文,监听取消信号
特性:
- 提供
Done
通道,通知取消事件 - 父上下文取消时,子上下文也会被取消
- 提供
生成方式:
context.WithCancel(parent Context) (ctx Context, cancel CancelFunc)
context.WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)
说明:
- 返回一个
cancelCtx
类型的实例和一个取消函数。 - 调用取消函数时,会触发
Done
通道,并通知所有派生的子上下文 WithCancelCause
的取消函数可以设置附加的错误
- 返回一个
- 使用场景:需要手动控制操作取消的场景
context.timerCtx
type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time }
- 描述:带有超时或截止时间的上下文。
特性:
- 包含一个定时器,到期后会自动取消上下文
- 可以嵌套父上下文的取消逻辑。
生成方式:
context.WithTimeout(parent Context, timeout time.Duration)
context.WithDeadline(parent Context, deadline time.Time)
WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)
WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)
说明:
WithTimeout
基于相对时间生成超时上下文WithDeadline
基于绝对时间生成超时上下文- 超时后,
Done
通道会触发,标记上下文已取消
- 使用场景:处理需要设定超时时间的操作
context.valueCtx
type valueCtx struct { Context key, val any }
- 描述:携带键值对的上下文
特性:
- 提供键值对存储功能
- 数据是只读的,不允许修改
生成方式:
context.WithValue(parent Context, key, val any) Context
说明:
- 生成一个
valueCtx
实例,携带指定的键值对。 - 适用于在上下文中传递特定信息,例如请求 ID、用户权限等。
- 生成一个
- 使用场景:在跨
API
边界传递数据,例如用户身份、请求 ID 等
context.withoutCancelCtx
type withoutCancelCtx struct { c Context }
- 描述:从父 Context 派生的上下文,但该上下文不会受到父上下文的取消或超时的影响
特性:
Done()
方法始终返回nil
,表示上下文永远不会被取消。- 仍然可以通过
Value
和Deadline
方法访问父上下文的值和超时时间(如果有)
生成方式:
context.WithoutCancel(parent Context) Context
说明:
- 生成一个
withoutCancelCtx
实例,用于需要持续存在或不受父上下文生命周期影响
- 生成一个
- 使用场景:需要长时间运行的操作、避免误取消
应用场景实例
控制子协程退出
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
go worker(ctx)
time.Sleep(2 * time.Second)
fmt.Println("主业务异常,通知子协程退出....")
cancelFunc()
time.Sleep(1 * time.Second)
}
func worker(ctx context.Context) {
n := 0
for {
n++
select {
case <-ctx.Done():
fmt.Println("噢,卖糕的,我死了.....")
return
default:
fmt.Println("嘿嘿,处理业务....:", n)
time.Sleep(time.Second)
}
}
}
执行输出
go run main.go
嘿嘿,处理业务....: 1
嘿嘿,处理业务....: 2
主业务异常,通知子协程退出....
噢,卖糕的,我死了.....
超时退出
func main() {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*3)
defer cancelFunc()
go worker(ctx)
// 等待上下文的完成或超时
<-ctx.Done()
// 检查上下文的错误
if err := ctx.Err(); err != nil {
// 输出超时错误
fmt.Println("超时错误:", err)
} else {
fmt.Println("任务完美完成....")
}
}
func worker(ctx context.Context) {
// 模拟任务运行并定期检查上下文是否被取消
for i := 0; i < 5; i++ {
// 每秒执行一次任务
select {
case <-ctx.Done():
// 如果上下文被取消(超时或其他原因),退出任务
fmt.Println("任务超时,退出....")
return
default:
// 模拟任务的执行
fmt.Printf("正常处理业务 %d\n", i+1)
time.Sleep(1 * time.Second) // 模拟任务执行的延迟
}
}
// 如果没有超时,任务成功完成
fmt.Println("任务成功完成")
}
执行输出:
go run main.go
正常处理业务 1
正常处理业务 2
正常处理业务 3
超时错误: context deadline exceeded
传递值
值传递我们一般在api
入口处,传递一些全局的值,比如请求id,日志id,用户id等,整个生命周期保持一直的值
func main() {
ctx := context.WithValue(context.Background(), "request_id", "日志id")
ctx = context.WithValue(ctx, "user_id", "666")
go worker(ctx)
time.Sleep(1 * time.Second)
}
func worker(ctx context.Context) {
go worker1(ctx)
fmt.Println("worker:request_id", ctx.Value("request_id"))
fmt.Println("worker:user_id", ctx.Value("user_id"))
}
func worker1(ctx context.Context) {
fmt.Println("worker1:request_id", ctx.Value("request_id"))
fmt.Println("worker1:user_id", ctx.Value("user_id"))
}
go run main.go
worker:request_id 日志id
worker:user_id 666
worker1:request_id 日志id
worker1:user_id 666
使用注意
WithValue
传递值不应该被滥用,局部变量不应该使用它来存储- 取消函数不要在循环的使用
defer cancelFunc()
来延迟调用,应及时手动调用,防止内存泄漏
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。