2026-10-31·作者 Sijie Wang

Context

Context:取消、超时、请求数据,一路往调用树下走

动机:done channel 跨层就不好使了。 done 解决了“怎么停”,但真实程序里取消信号要穿透很多层:HTTP handler、service、repo、DB driver 或者下游调用。每层传一个 done channel,签名全被污染,还带不了超时、带不了请求数据。context 就是把 done 标准化、升级之后的东西:一个对象沿着调用链往下传,取消、超时、请求级数据三件事打包在一起。

接口四个方法。 Done() 返回那个 done channel,关了就是停。Err() 说为什么停,是取消还是超时。Deadline() 说有没有超时点、到哪个时间点。Value(key) 带请求级数据,比如 request ID,跟着这次请求一路走。

派生是树,取消只往下走。 从 Background 出发,用 WithCancel、WithTimeout、WithDeadline、WithValue 派生子 context。父取消,所有子一起取消,跟关 done 广播一个道理,只是成树了。子取消不影响父和兄弟。值是不可变的,派生出来的是新对象,父本身永远不变。

第一个参数,绝不塞 struct。 三点。生命周期对不上:context 属于一次请求,struct 常常长期存在,塞进去等于把一次请求的取消信号绑到长期对象上,请求结束了信号还挂着,或者反过来拿着已取消的 context 继续用。取消链断了:显式传参让每个人都看得到取消路径,藏进字段里,接手的人就不知道这函数会不会被取消。签名即文档:第一个参数一看就知道这函数可取消、有超时。

WithCancel、WithTimeout 返回的 cancel 一定要调。 惯例是 defer cancel()。不调,子 context 就挂在父的 children 里,直到父取消或者超时才释放,这就是 context 泄漏。WithTimeout 就是 WithDeadline(现在加一段时间),到点自动取消。Err() 能分清原因:手动取消是 Canceled,超时是 DeadlineExceeded。

使用形态就两种。 循环里 select 等 ctx.Done(),一关就 return ctx.Err();阻塞调用回来后查 Err(),分清是正常错还是取消/超时。往下游传就把同一个 ctx 交给支持它的调用:http.NewRequestWithContext、数据库的 QueryContext,一个 ctx 走完整条链。

Value 的规矩。 key 用自定义非导出类型,防不同包撞 key。只放请求级元数据:request ID、trace ID、auth 信息。业务对象不放,更不拿它传本该写进参数的东西。

几个不要。 不传 nil,没 context 用 Background、占位用 TODO;cancel 不忘调;不存 struct;不用 Value 传业务数据或可选参数。

React 类比,以及它的边界。 React Context 解决 prop drilling,Go context 解决 done drilling:都是把要穿透多层的东西从中间每一层的签名里抽出来。两处差别。React Context 带的是 UI 状态,变了会触发重渲染;Go context 带的是控制信号和请求元数据,不是业务状态。React Context 是隐式的,哪里用 hook 哪里就能拿到;Go context 是显式的,必须当第一个参数传下去,沿着不可变的树单向流动。

生产注记。 web 服务里,每个请求从 HTTP 请求 context 派生自己的 context,超时设在层与层的边界,服务器关闭时取消在途请求的 context。context 就是把“客户端走了”变成“活儿真的停了”的东西。

關於這一條

sijie 的一條 wiki 條目。本站的 AI 以同一套語料庫為依據、以 sijie 的口吻作答,並附有指向此類條目的引用——回答會花 sijie 的錢,所以它在存取碼後等待: 輸入存取碼 →