golang context 操作
context 描述
go 1.7 版本之后加入一个新的标准库context 它定义了Context 类型 ,专门用来简化对于处理单个请求的多个goroutine之间与请求域的数据、
取消信号、截止时间等相关操作,这些操作可能涉及到多个API调用。
对服务器传入的请求应该创建上下文,而对服务器的传出调用的应该接收上下文,它们之间的函数调用链必须传递上下文,或者可以使用
WithCancel、WithDeadline 、WithTimeout、WithValue 创建的派生上下文,当一个上下文被取消时,它派生的所有上下文也被取消。
background 和 todo 本质上都是emptyCtx 结构类型,是一个不可取消,没有设置截止时间, 没有携带任何值的Context
context 使用场景
在go http 包的server中 每一个请求在都有一个对应的goroutine去处理,请求处理函数通常会启动额外的goroutine用来访问后端服务例如数据库和RPC服务
用来处理一个请求的goroutine通常需要访问一些请求特定的数据,例如终端用户的身份认证信息,验证相关的token、请求的截止时间
当一个请求取消或者超时,所有用来处理该请求的goroutine 都应该迅速退出,然后系统才会释放这些goroutine占用资源
Background()TODO()
go 内置两个函数: Background() 和 TODO()这两个函数分别返回一个实现了 Context 接口 的background 和todo 我们代码中最开始都是以两个内置的上下文
对象作为最顶层的partent context 衍生出更多的子上下文对象
Background 主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context 也就是根Context
TODO() 它目前还不知道具体使用场景,如果我们不知道该使用什么context 的时候,可以使用这个
没有context 功能时实现goroutine 通知退出操作
1. 通过变量方式通知的
package main var wg sync.WaitGroup var notify bool func demo(){ var i int defer wg.Done() for { i++ fmt.Printf("判断的方式 执行%v次\n",i) time.Sleep(time.Millisecond * 500) if notify{ break } } } // 使用判断参数退出子goroutine func main(){ wg.Add(1) go demo() time.Sleep(time.Second * 3 ) notify = true fmt.Println("判断方式退出子goroutine")
wg.wait() }
2. 通过channel 方式通知子goroutine 退出
package main var exitChan chan bool var wg sync.WaitGroup func demo1(){ defer wg.Done() var i int // FORLOOP 跳出指定循环体 EXITFOR: for { i++ fmt.Printf("channel方法, 执行%v次\n",i) time.Sleep(time.Millisecond * 500 ) select { case <- exitChan: break EXITFOR default: } } } func main(){ exitChan = make(chan bool,1) wg.Add(1) go demo1() time.Sleep(time.Second * 3 ) exitChan <- true fmt.Println("channel方法退出子goroutine") wg.Wait() }
以上的使用方法比较单一,如果涉及到嵌套的子goroutine 时就无法通知嵌套里面的子goroutine 这时就可以利用context 方法了
context.WithCancel
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// context 方法退出子goroutine
func woker01(ctx context.Context){
defer wg.Done()
go woker02(ctx)
var i int
// FORLOOP 跳出指定循环体
EXITFOR:
for {
i++
fmt.Printf("woker02 context方法, 执行%v次\n",i)
time.Sleep(time.Millisecond * 500 )
select {
case <- ctx.Done():
break EXITFOR
default:
}
}
}
func woker01(ctx context.Context){
defer wg.Done()
var i int
// FORLOOP 跳出指定循环体
EXITFOR:
for {
i++
fmt.Printf("woker01 context方法, 执行%v次\n",i)
time.Sleep(time.Millisecond * 500 )
select {
case <- ctx.Done():
break EXITFOR
default:
}
}
}
func main(){
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go woker02(ctx)
time.Sleep(time.Second * 3 )
fmt.Println("context 退出子goroutine")
cancel()
wg.wait()
}
context WithDeadline
尽管ctx会过期,但是在任何情况下调用它的cancel 函数都是很好的实践如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间
package main import ( "context" "fmt" "sync" "time" ) /* context WithDeadline */ var wg sync.WaitGroup func contextDeadline(){ d := time.Now().Add(50 * time.Millisecond) ctx, cancel := context.WithDeadline(context.Background(),d) defer cancel() select{ case <- time.After(1 * time.Second): fmt.Println("zhangsan") case <- ctx.Done(): fmt.Println(ctx.Err()) } } func main(){ contextDeadline() }
context.WithTimeout 和 context.WithDeadline 类似都是超时退出
package main import ( "context" "fmt" "sync" "time" ) var wg sync.WaitGroup func woker(ctx context.Context){ exitfor: for { fmt.Println("connect mysql databases.....") time.Sleep(time.Millisecond * 10) select{ case <-ctx.Done(): break exitfor default: } } fmt.Println("woker done!") wg.Done() } func contextTimeout(){ ctx, cancel :=context.WithTimeout(context.Background(),time.Millisecond * 50 ) wg.Add(1) go woker(ctx) time.Sleep(time.Second * 5) cancel() wg.Wait() fmt.Println("runnning close") } func main(){ contextTimeout() }
context.WithValue
仅对API和进程之间传递请求域的数据使用上下文值,而不是使用它传递可选参数,所提供的键必须可比较的并且不应该是string 类型或任何其他类型,以免使用上下文在包之间发生冲突。
WithValue 的用户应该为键定义自己的类型,为了避免在分配给interface{} 时进行分配,上下文键通常具有具体类型struct{} 。或者导出的上下文呢关键变量的静态类型应该是指针或接口
package main import ( "context" "fmt" "sync" "time" ) type TraceCode string var wg sync.WaitGroup /* context.WithValue */ func wokerValue(ctx context.Context){ key := TraceCode("TRACE_CODE") traceCode , ok := ctx.Value( key).(string) if !ok{ fmt.Println("invalid trace code") } Loop: for { fmt.Printf("woker, trace code:%s\n",traceCode) time.Sleep(time.Millisecond * 10) select{ case <- ctx.Done(): break Loop default: } } fmt.Println("workerValue") wg.Done() } func contextValue(){ ctx, cancel := context.WithTimeout(context.Background(),time.Millisecond*50) ctx = context.WithValue(ctx,TraceCode("TRACE_CODE"),"1233221112332") wg.Add(1) go wokerValue(ctx) time.Sleep(time.Second *5 ) cancel() wg.Wait() fmt.Println("context value done!") } func main(){ contextValue() }
使用context 的注意事项
1. 推荐以参数的方式显示传递Context
2. 以Context作为参数的函数方法,应该把Context作为第一个参数。
3. 给一个函数方法传递Context的时候,不要传递nil ,如果不知道传递什么,就使用context.TODO()
4. Context的Value相关方法应该传递请求域的必要参数,不应该用于传递可选参数
5. Context是线程安全的,可以放心的在多个goroutine中传递
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南