7 context

7.1 context是什么?

主要是用来在goroutine之间传递上下文,包括:取消信号,超时时间,截至时间,k-v等等
随着context包的引入,标准库中很多接口都加上了context参数,使用 context 几乎成为并发控制和超时控制的标准做法,与它协作的 API 都可以由外部控制执行“取消”操作,例如:取消一个 HTTP 请求的执行。

另外,context.Context 可以协调多个 goroutine 中的代码执行“取消”操作,并且可以存储键值对,最重要的是它是并发安全的操作

在 Go 的后台服务中,一个请求通常会启动多个 goroutine 协同工作:有的查内存缓存,有的查数据库,有的调第三方接口。这些 goroutine 需要共享一些请求级别的信息,比如登录 token、超时时间等。当请求被取消(比如用户关掉浏览器)或超时了,所有为这个请求工作的 goroutine 都应该快速退出,释放资源。如果没有超时控制,一旦下游服务变慢,等待的 goroutine 会越积越多,导致内存暴涨、调度器压力剧增,最终引发雪崩效应,整个服务不可用。

context 包正是为了解决这个问题而生的:它在一组 goroutine 之间传递共享值、取消信号和 deadline(最后期限)。在 Go 里,不能直接杀死一个 goroutine,通常用 channel 和 select 来控制退出,但当一个请求衍生出多个相互关联的 goroutine 时,用 context 会更方便。context 本质上是一个树形结构,通过 context.Background() 创建根节点(一个空 context,不能被取消,没有值也没有超时),然后用四个函数创建子节点:WithCancel(可以手动取消)、WithDeadline(在指定时间点取消)、WithTimeout(在指定时长后取消)、WithValue(携带键值对数据)。这些子节点会继承父节点的特性,当父节点被取消时,所有子孙节点也会收到取消信号。

使用 context 有几点建议:不要把 context 塞进结构体里,而是作为函数的第一个参数,参数名通常叫 ctx;不要传 nil context,实在不知道传什么就用 context.TODO();不要把本该作为函数参数的数据塞进 context 里,context 应该存的是请求级别的共享数据,比如 session、token 等;同一个 context 可能会被多个 goroutine 同时使用,别担心,context 是并发安全的。

简单说,context 就是 goroutine 之间的“传话筒”和“遥控器”:传话筒负责传递请求范围内的公共信息,遥控器负责一键取消所有相关 goroutine 的工作,防止资源浪费和服务雪崩。

7.3 如何使用context

传递共享数据:在 Web 服务端开发中,往往需要将请求处理的整个过程串起来,比如传递 Request-ID 用于日志追踪,但 Go 没有 ThreadLocal 的概念,因此需要在函数调用时显式传递 context。通过 context.WithValue 创建携带键值对的 context,然后在子函数中用 ctx.Value 取出数据。实际项目中,通常会写一个中间件,从 HTTP 请求头中提取 Request-ID 并放入 context,后续的处理函数就可以通过 context 拿到这个 ID 用于日志记录等。注意,key 建议使用自定义类型而非基础类型,避免不同包之间的 key 冲突。

定时取消:当需要控制操作的最大执行时间时,可以使用 context.WithTimeout 或 context.WithDeadline 创建一个带超时的 context。例如从网络获取数据,如果 1 秒内没返回就放弃。在业务代码中通过 select 同时监听正常返回和 ctx.Done(),哪个先返回就执行哪个。如果超时了,ctx.Done() 会返回一个已关闭的 channel,触发超时处理逻辑(比如返回默认值或错误)。这里有个细节:WithTimeout 返回的 context 和 cancel 函数是分开的,cancel 函数只能由外层调用,这样保证了取消信号只能从父节点流向子节点,避免子节点擅自取消导致混乱。

防止 goroutine 泄漏:goroutine 泄漏是指 goroutine 永远无法退出,一直占用资源。经典场景是:一个函数内部启动了 goroutine 无限产生数据,但调用者只需要前几个就 break 退出了,导致那个 goroutine 永远阻塞在发送操作上。通过 context 可以优雅地解决:把 context 传入 goroutine,在 for 循环中用 select 同时监听发送操作和 ctx.Done(),当外层调用 cancel 时,goroutine 收到取消信号立即 return 退出,资源被回收。这样就能确保 goroutine 的生命周期可控,避免泄漏。

总结:context 的三个核心用法——传值用 WithValue,超时用 WithTimeout/WithDeadline,主动取消用 WithCancel。记住口诀:context 要作为函数第一个参数,不要塞进结构体;用完记得 defer cancel() 防止资源泄漏;值传递用自定义 key 类型避免冲突。

7.4 context底层原理是什么

image

posted @ 2026-03-30 13:59  cyusouyiku  阅读(2)  评论(0)    收藏  举报