不调用 cancel() 会导致 context 树分支永久存活,关联的 timer、channel、goroutine 无法被 gc,http 或数据库连接可能长期占用;正确做法是 defer cancel() 确保函数退出时释放资源。

Context 不能靠全局变量或函数参数手动透传,必须从入口显式向下传递;不主动 cancel 的 context.Background() 或 context.WithCancel() 会泄漏 goroutine 和底层资源。
为什么 context.WithCancel 返回的 cancel 函数必须调用
不调用 cancel() 会导致:context 树中该分支永远存活,关联的 timer、channel、goroutine 不会被 GC;若该 context 被用于 http.Client 或数据库查询,可能长期占用连接或超时逻辑失效。
常见错误场景:
- 在 defer 中忘记调用
cancel()(尤其嵌套多层函数时) - 只在成功路径调用
cancel(),panic 或 error 分支遗漏 - 将
cancel函数传给 goroutine 后,在 goroutine 外提前调用,导致子任务被误中断
正确做法:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 即使后续 panic 也会执行 resp, err := http.DefaultClient.Do(req.WithContext(ctx))
context.WithValue 只能传请求级只读数据,禁止传业务结构体或函数
context.WithValue 是为传递如 trace ID、user ID、request ID 这类轻量、不可变、跨中间件共享的元数据设计的。它不是通用状态容器。
容易踩的坑:
- 传入指针或 struct,下游修改后影响其他协程 ——
context是共享的,没有并发安全保证 - 用
map[string]interface{}包装业务参数,掩盖真实依赖,破坏可测试性 - 类型断言失败不处理,直接 panic(应始终检查 ok)
示例(安全用法):
type ctxKey string
const userIDKey ctxKey = "user_id"
<p>ctx = context.WithValue(ctx, userIDKey, "u_12345")
// 使用时:
if uid, ok := ctx.Value(userIDKey).(string); ok {
log.Printf("user: %s", uid)
}
</p>
HTTP handler 中如何正确派生并传播 context
HTTP server 默认为每个请求生成一个 context.Request.Context(),它已集成超时、取消信号(如客户端断连)。不应丢弃它,而应在其基础上派生子 context。
关键点:
- 不要用
context.Background()替换请求 context,否则丢失 client 断开通知 - 数据库查询、下游 HTTP 调用等 I/O 操作,应使用派生出的带 timeout 的 context
- goroutine 启动前必须显式传入 context,且监听
ctx.Done()退出
反模式:
// ❌ 错误:丢弃 request context,自建 background
go func() {
time.Sleep(10 * time.Second) // 无法响应 client 断连
}()
<p>// ✅ 正确:基于 req.Context() 派生,并监听 Done
ctx, cancel := context.WithTimeout(r.Context(), 8<em>time.Second)
defer cancel()
go func(ctx context.Context) {
select {
case time.Second):
// do work
case </em></p><h3>自定义操作耗时长时,<code>context.Context</code> 的 Done channel 必须被监听</h3><p>任何可能阻塞的操作(文件读写、channel receive、锁等待、sleep)都应配合 <code>ctx.Done()</code> 做中断判断。Go 标准库多数 I/O 接口(如 <code>net.Conn</code>, <code>http.Client</code>, <code>database/sql</code>)已支持 context,但自定义逻辑不会自动感知。</p><p>典型遗漏点:</p>
- for 循环中未定期检查
ctx.Err() != nil - select 中漏掉
分支,导致 goroutine 卡死 - 用
time.AfterFunc延迟执行但没绑定 context 生命周期
简单循环中断示例:
for i := 0; i <p>最常被忽略的是:context 不是“设置一次就完事”的开关,它是协作式取消协议 —— 每一层调用者都要主动检查、转发、清理。一旦某层没监听 <code>Done()</code> 或忘了 <code>cancel()</code>,整条链路就失去控制力。</p>
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











