Go Context
一、介绍
Context (上下文) 是 Go 语言标准库 context 包提供的核心工具。
用于在goroutine之间传递取消信号、超时控制,元数据等。
二、接口定义
go
type Context interface {
// 取消信号:返回一个通道,当Context被取消/超时时,通道会关闭
Done() <-chan struct{}
// 取消原因:返回Context结束的原因(nil表示未结束)
Err() error
// 超时时间:返回Context的截止时间(ok=false表示无截止时间)
Deadline() (deadline time.Time, ok bool)
// 元数据:根据key获取绑定的value(协程安全)
Value(key any) any
}
2.1 Done()
如果 Done 没有被 close,Err 方法返回 nil;如果 Done 被 close,Err 方法会返回 Done 被 close 的原因
三、Context 的创建方式
context 包提供了 4 种常用的 Context 创建函数,遵循父子继承原则(子 Context 基于父 Context 派生,父取消则子全部取消)
3.1 根 Context:background & todo
go
func Background() Context {
return backgroundCtx{}
}
func TODO() Context {
return todoCtx{}
}
Background()和 TODO()是两个用于创建根上下文(root context)的函数
3.1.1 Background()函数
- 作用 :返回一个非nil 的 、空的 Context。它没有任何值,不会被 cancel,不会超时,也没有截止时间。
- 典型用途 :
*- 作为 main函数的初始上下文。
-
- 作为初始化或测试的顶级上下文。
-
- 作为传入请求的根上下文。
- 使用场景 :
- 当没有其他上下文可用时,应使用 Background()作为起点。
- 通常用于主函数、初始化或测试中。
go
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background() // 根Context
// ctx := context.TODO() // 临时占位,和Background等价
fmt.Println("Done channel:", ctx.Done()) // nil(根Context不会取消)
fmt.Println("Err:", ctx.Err()) // nil
dateTime, ok := ctx.Deadline()
fmt.Println("Is Deadline:", ok) // false
fmt.Println("Deadline:", dateTime) // 0001-01-01 00:00:00 +0000 UTC
fmt.Println("Value:", ctx.Value("key")) // nil
}
PS E:\goCode\context> go run .\main.go
Done channel: <nil>
Err: <nil>
Is Deadline: false
Deadline: 0001-01-01 00:00:00 +0000 UTC
Value: <nil>
PS E:\goCode\co
3.1.2 TODO()函数
- 作用:同样返回一个非nil 的、空的 Context。其语义与 Background()相同。
- 使用场景:
*- 当不确定该使用哪个 Context时。
-
- 当周围的函数尚未扩展以接受 Context参数时(作为占位符使用)。
- 注意:应避免在生产代码中保留 TODO(),它仅作为临时使用,提示开发者将来需要替换为合适的上下文
go
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.TODO() // 根Context
fmt.Println("Done channel:", ctx.Done()) // nil(根Context不会取消)
fmt.Println("Err:", ctx.Err()) // nil
dateTime, ok := ctx.Deadline()
fmt.Println("Is Deadline:", ok) // false
fmt.Println("Deadline:", dateTime) // 0001-01-01 00:00:00 +0000 UTC
fmt.Println("Value:", ctx.Value("key")) // nil
}
PS E:\goCode\context> go run .\main.go
Done channel: <nil>
Err: <nil>
Is Deadline: false
Deadline: 0001-01-01 00:00:00 +0000 UTC
Value: <nil>
3.1.3 区别
Background()是默认的起始上下文,应在已知用途时使用。TODO()是临时占位符,表明上下文尚未确定,需要在未来替换
3.2 可取消 Context:WithCancel
go
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := withCancel(parent)
return c, func() { c.cancel(true, Canceled, nil) }
}
3.2.1 核心功能
- 创建派生上下文:基于父上下文创建新的上下文
- 提供取消机制:返回一个可取消的上下文和对应的取消函数
- 传播取消信号:新上下文会监听父上下文的取消信号
3.2.2 关键特性
-
取消触发条件
返回的上下文会在以下任一情况发生时关闭其 Done通道
- 调用返回的 cancel函数
- 父上下文的 Done通道被关闭
-
取消传播机制
- cancel 是向下传递的
- 子上下文的取消不会影响父上下文
- 父上下文的取消会自动传播到所有子上下文
-
调用
cancel释放相关资源不是只有你想中途放弃,才去调用
cancel,只要你的任务正常完成了,就需要调用cancel,否则,资源不能得到释放。
go
// 最佳实践是尽快调用 cancel
defer cancel() // 通常使用 defer 确保调用
3.2.3 用途
需要主动取消长时间的任务
go
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("worker done")
return
case <-time.After(time.Second):
fmt.Println("worker working")
}
}
}
func main() {
// 创建可取消的上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保最终调用取消,避免Goroutine泄漏
go worker(ctx)
time.Sleep(5 * time.Second)
cancel()
time.Sleep(time.Second)
fmt.Println("main done")
}
PS E:\goCode\context> go run .\main.go
worker working
worker working
worker working
worker working
worker done
main done
级联
ctx1被取消 → ctx2也被取消
go
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 创建根上下文
rootCtx := context.Background()
// 创建子上下文1
ctx1, cancel1 := context.WithCancel(rootCtx)
defer cancel1()
// 创建子上下文2(从ctx1派生)
ctx2, cancel2 := context.WithCancel(ctx1)
defer cancel2()
go func() {
<-ctx2.Done()
fmt.Println("ctx2 被取消:", ctx2.Err())
}()
go func() {
<-ctx1.Done()
fmt.Println("ctx1 被取消:", ctx1.Err())
}()
cancel1()
time.Sleep(100 * time.Millisecond)
}
PS E:\goCode\context> go run .\main.go
ctx2 被取消: context canceled
ctx1 被取消: context canceled
3.3 超时 Context:WithTimeout
go
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
指定超时时间,超时后自动取消
go
package main
import (
"context"
"fmt"
"time"
)
func request(ctx context.Context, url string) {
select {
case <-ctx.Done():
fmt.Printf("请求 %s 失败:%v\n", url, ctx.Err())
return
case <-time.After(1 * time.Second): // 模拟请求耗时1秒
fmt.Printf("请求 %s 成功\n", url)
}
}
func main() {
// 创建超时Context(1.5秒超时)
ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond)
defer cancel()
// 启动请求协程
go request(ctx, "https://example.com")
// 等待结果
time.Sleep(2 * time.Second)
fmt.Println("主协程:结束")
}
3.4 截止时间 Context:WithDeadline
指定具体截止时间(比如 time.Date(2026, 3, 22, 10, 0, 0, 0, time.Local)),到达时间后自动取消
go
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 设定截止时间为1秒后
deadline := time.Now().Add(1 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
select {
case <-time.After(2 * time.Second):
fmt.Println("操作完成")
case <-ctx.Done():
fmt.Println("操作超时:", ctx.Err()) // context deadline exceeded
}
}
3.5 带元数据 Context:WithValue
用于传递少量协程安全的元数据(比如请求 ID、用户 Token),key 建议定义为自定义类型(避免全局 key 冲突)
go
package main
import (
"context"
"fmt"
)
// 定义自定义key类型(避免冲突)
type ctxKey string
const (
RequestIDKey ctxKey = "request_id"
UserIDKey ctxKey = "user_id"
)
func handler(ctx context.Context) {
// 获取元数据
reqID := ctx.Value(RequestIDKey).(string)
userID := ctx.Value(UserIDKey).(int)
fmt.Printf("处理请求:reqID=%s, userID=%d\n", reqID, userID)
}
func main() {
// 基于根Context添加元数据
ctx := context.WithValue(context.Background(), RequestIDKey, "req-123456")
ctx = context.WithValue(ctx, UserIDKey, 1001) // 链式添加
handler(ctx)
}
四、使用注意
- 不要存储 Context:Context 应通过参数传递,而非存储在结构体 / 全局变量中。
- 根 Context 选择 :生产环境用
context.Background(),临时占位用context.TODO()。 - 取消必调用 :
WithCancel/WithTimeout/WithDeadline返回的cancel函数必须调用(即使提前完成),避免 Goroutine 泄漏。 - 参数传递 :Context 作为函数第一个参数,命名为
ctx(比如 func doSomething(ctx context.Context, arg int))
五、常见使用场景
- HTTP 服务:控制请求超时(比如 Gin/Net/http 中传递 Context);
- 数据库操作:取消慢查询(如 sql.DB.QueryContext(ctx, sql));
- RPC 调用:跨服务传递取消信号(如 gRPC 内置 Context 支持);
- 批量任务 :取消未完成的子任务(比如批量处理文件时,某个任务失败则全部取消)。