Context 用于叫停并发而非启动,并发需监听 ctx.Done() 并由发起方调用一次 cancel 函数;否则易致 goroutine 泄漏、CPU 拉满、连接堆积。

Context 不是用来“启动并发”的,而是用来“叫停并发”的。 它解决的不是“怎么开多个 goroutine”,而是“怎么让它们在该停的时候一起停”。不加 Context 的并发,就像放出去一群没 leash 的狗——跑得欢,但喊不回来,线上一压就 goroutine 泄漏、CPU 拉满、连接堆积。
context.WithCancel 怎么用才不会泄漏
手动取消是 Context 最基础也最容易翻车的用法。核心就两点:cancel 函数必须调用,且只能由发起方调一次;worker 内部必须监听 ctx.Done() 并立即退出。
- 错误示范:
go func() { doWork() }()—— 没传 ctx,也没监听,cancel 了也毫无反应 - 正确写法:worker 函数签名带
ctx context.Context,循环里用select { case -
cancel()一定要defer cancel()(除非你明确控制调用时机),否则函数提前 return 就漏掉了 - 别把
cancel函数传进多个 goroutine 并发调用——会 panic,它不是线程安全的
context.WithTimeout 为什么比 time.Sleep 更可靠
time.Sleep 是“我睡我的”,context.WithTimeout 是“大家共用一个倒计时”。前者无法中断阻塞系统调用(比如 http.Client.Do、database/sql.Query),后者能穿透到底层,强制断开连接。
- HTTP 请求中,用
http.NewRequestWithContext(ctx, ...)才能让超时真正生效;只在业务层 sleep 没用 -
WithTimeout和WithDeadline本质一样,前者是相对时间(3 * time.Second),后者是绝对时间(time.Now().Add(...)) - 超时后
ctx.Err()返回"context deadline exceeded",不是nil,记得判错处理 - 超时 ctx 也要
defer cancel(),否则底层 timer 不释放,长期运行会内存缓慢增长
context.WithValue 什么时候该用、怎么避免踩坑
它只适合传请求域元数据(如 "traceID"、"userID"),不是通用参数传递工具,更不是配置中心。
立即学习“go语言免费学习笔记(深入)”;
- key 必须是自定义类型(比如
type userIDKey struct{}),不能用string直接当 key,否则跨包容易冲突 - 值必须是只读的,不要传指针或可变结构体,下游修改会影响上游
- 不要用
WithValue传业务逻辑需要的参数(比如数据库连接、logger 实例)——这些应该显式传参或依赖注入 -
ctx.Value(key)返回interface{},务必做类型断言,且检查 ok:if v, ok := ctx.Value(myKey).(string); ok { ... }
最常被忽略的一点:Context 树的取消是单向广播,不可逆。一旦父 ctx 被 cancel,所有子 ctx 立即失效,哪怕你刚派生出来还没来得及用。所以别在 handler 里临时派生一个 timeout ctx 去调用下游,再拿同一个 ctx 去记录日志——日志可能根本写不出去。










