必须用 context.context 而非自定义 done chan,因其支持超时、嵌套取消、安全传递元数据,且被标准库广泛原生支持;withcancel、withtimeout、withdeadline 分别用于手动取消、相对超时、绝对截止时间;http handler 中应复用 r.context() 而非 context.background();context.value 仅限存轻量、不可变、请求级元数据,键需自定义类型,避免滥用。

为什么必须用 context.Context 而不是自己传 done chan struct{}
直接传 done 通道看似简单,但无法携带超时时间、无法嵌套取消、不能安全传递请求元数据(比如 trace ID),且多个 goroutine 协作时容易漏关或重复关 channel。而 context.Context 是标准库统一抽象,所有 HTTP handler、database/sql、net/http.Client 等都原生支持它——你不用说服每个依赖库适配你的私有信号机制。
context.WithCancel、context.WithTimeout、context.WithDeadline 怎么选
三者本质都是返回一个新 Context 和对应的取消函数,区别只在触发条件:
-
context.WithCancel(parent):纯手动控制,调用返回的cancel()就立刻取消 -
context.WithTimeout(parent, 5*time.Second):从调用时刻起倒计时,适合 RPC 调用、数据库查询等“耗时上限明确”的场景 -
context.WithDeadline(parent, time.Now().Add(5*time.Second)):指定绝对截止时间,适合与外部系统对齐 deadline(如上游 HTTP 请求带Timeoutheader)
注意:WithTimeout 底层就是 WithDeadline 的封装,不要混用;所有子 context 必须显式调用 cancel(),否则 parent 的资源(如 timer)不会释放。
在 HTTP handler 里怎么正确注入 context.Context
Go 的 http.ServeHTTP 默认把 request 的 context 透传给 handler,你不需要手动创建 root context:
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
func handler(w http.ResponseWriter, r *http.Request) {
// r.Context() 已自带 cancel/timeout,来自 net/http 包内部管理
ctx := r.Context()
// 若需加额外约束(如限制 DB 查询最多 2s)
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel() // 必须 defer,否则泄漏 timer
// 传给下游函数
result, err := db.Query(ctx, "SELECT ...")
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
http.Error(w, "DB timeout", http.StatusGatewayTimeout)
return
}
http.Error(w, "DB error", http.StatusInternalServerError)
return
}
// ...
}
常见错误:在 handler 内部新建 context.Background() 替代 r.Context() —— 这会丢失请求生命周期绑定,导致超时/取消信号失效,也断开 tracing 链路。
往 context 里塞值(Value)的边界在哪
context.Value 只能存轻量、请求级、不可变的元数据,比如 requestID、userID、authToken。它不是通用对象容器:
- 键必须是自定义类型(避免字符串冲突),例如
type ctxKey string; const userIDKey ctxKey = "user_id" - 值不能是大结构体或指针(可能引发内存泄漏),更不能存可变状态(如 mutex、channel)
- 绝不用于传递业务参数(如
page_size),那该走函数参数 - 取值时务必做类型断言检查,
v := ctx.Value(userIDKey); if v != nil { id := v.(string) }
最易被忽略的是:context.Value 不是线程安全的,但它的设计本意就是只读传递——写操作只发生在创建子 context 时(context.WithValue),之后所有下游读取都是安全的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










