context包在并发编程中的角色
Go的context包是并发编程的标配,核心用途有两个:跨goroutine传递取消信号,携带请求级元数据。服务端每个请求从入口创建context,一路传给所有下游调用,任何一个环节超时或取消,整条链路能及时收手,避免goroutine泄漏。
context在net/http的Request中默认绑定,r.Context()就是请求生命周期。handler返回后这个context会取消,后台goroutine需要自己判断是否提前退出。
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
go doSlowWork(ctx) // 传入ctx,而非闭包捕获
}
取消传递:WithCancel与子context继承
WithCancel基于父context派生,调用cancel()后,该context和它的所有后代都会收到取消信号。典型的级联取消:主请求取消,所有并行子任务同时停止。
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 函数退出时释放资源
go func() {
select {
case <-ctx.Done():
return // 收到取消信号
case res := <-jobChan:
process(res)
}
}()
cancel必须被调用,否则context和它的goroutine资源无法回收。defer cancel()是标准做法,即使函数提前返回也能释放。
超时控制:WithTimeout与Deadline设计
WithTimeout在指定时间后自动取消,适合限制外部API调用、数据库查询的等待上限。数据库驱动、HTTP客户端都原生支持传入context,超时后底层连接立即终止。
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT ...")
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Println("查询超时")
}
return
}
检查错误时用errors.Is(err, context.DeadlineExceeded)区分超时与普通错误,比比较字符串可靠。多层嵌套服务要把超时预算逐层缩小,比如网关5秒,下游数据库3秒,避免单层超时拖垮整体。
Context传值的约束与替代方案
WithValue用于跨层传递请求级元数据(trace ID、用户身份)。官方建议key用自定义私有类型,避免string key包冲突。只在真正需要时用,别把参数也塞进context。
type ctxKey int
const userKey ctxKey = 1
ctx = context.WithValue(ctx, userKey, "user_1001")
u, ok := ctx.Value(userKey).(string)
日志追踪的标准做法是把traceID通过中间件写入context,日志库从context自动提取。context.Context不是替代函数参数的工具,能明确传的参数就明确传。
context陷阱:泄漏、滥用与测试
常见问题:一是不传context导致goroutine无法取消,压测时goroutine数持续上升;二是把context存进struct字段,官方的规范是context作为函数第一个参数显式传递;三是在context上挂大对象,GC压力大。
测试时用t.Context()拿测试上下文,或构造可控取消的context,配合select验证超时路径。go vet能静态检查出context误用,写测试时同步跑一遍vet。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/go-yu-yan-context-bao-shi-zhan-goroutine-qu-xiao-yu-chao/