Loading... # golang标准库context ## 一、context简介 context 是 go 官方在1.7版本引入标准库中的一个包,context 包主要用来简化并发(协程)控制。 > context 包的核心数据结构为 Context 接口,接口定义如下: ``` type Context interface { // Deadline 如果Context有超时限制,该方法会返回Context将被取消的时间 Deadline() (deadline time.Time, ok bool) // Done 返回一个通道,当Context取消或超时,该通道关闭 Done() <-chan struct{} // Err 在Done通道关闭后给出该Context取消的原因 Err() error // Value 返回该Context携带的变量 Value(key interface{}) interface{} } ``` > context包方法 ``` // CancelFunc取消Context type CancelFunc func() // WithCancel 返回父Context的拷贝和cancel方法,当父Context的Done通道关闭或cancel方法被调用时,该拷贝的Done通道被关闭 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) // WithTimeout 返回父Context的拷贝和cancel方法,当父Context的Done通道关闭或cancel方法被调用或达到设定的超时期限时,该拷贝的Done通道关闭 func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) // WithValue 返回携带了传入的key-value的父Context的拷贝 func WithValue(parent Context, key interface{}, val interface{}) Context ``` > 初始化的Context,它永远不会被取消 ``` // Background 返回一个空的Context。它永远不会被取消,也没有超时,并且不携带任何值, // Background 通常作为顶级的Context用在main方法、init方法和tests中 func Background() Context ``` *context.Todo()* 方法,当不确定创建的context用来做什么时可以使用 Todo()方法。 需要注意的是 Todo 和 Background 只是表达意义不同,代码层面都是实例化的同一个emptyCtx,就是说无论使用哪个,代码逻辑不会有任何区别。 ## 二、context使用 ### 1、实现超时控制 ``` func main() { r := gin.Default() r.GET("/run", func(c *gin.Context) { // 创建一个超时时间为3秒的 context ctx, _ := context.WithTimeout(c, time.Second*3) // 模拟启动一个任务处理协程 传入 context go func(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("exit") return default: } // 执行任务中 fmt.Println("do something") time.Sleep(time.Second) } }(ctx) c.JSON(200, gin.H{ "message": "done", }) }) r.Run() } ``` ### 2、实现并发控制 ``` func main() { r := gin.Default() r.GET("/run", func(c *gin.Context) { // 创建一个带有 cancel 的 context ctx, cancel := context.WithCancel(c) // 模拟启动一个任务处理协程 传入 context go func(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("exit") return default: } // 执行任务中 fmt.Println("do something") time.Sleep(time.Second) } }(ctx) // http 处理完成 通知所有任务退出 cancel() c.JSON(200, gin.H{ "message": "done", }) }) r.Run() } ``` ## 三、项目实践 ### 1、kratos项目中的最佳用法 - 在各个层内实现的方法第一个参数都指定为(ctx context.Context) - 在Context被传递到方法中,如果存在可能的异常超时等情况,应该对Context的Done信道(channel)进行监控,一旦该信道被关闭(即上层运行环境执行了 Cancel 或者超时),应主动终止对当前请求信息的处理,释放资源并返回。 - 在需要并发或者超时控制的时候调用子服务/方法时应该传入Context。 - 在调用子组件时应该尽量使用带有 context 参数的方法。例如使用标准库的 http包时,应该使用http.NewRequestWithContext而不是 http.NewRequest。 - 因为 context 可以在共享存储变量的特性,一般会将该请求的标识信息写入 context,比如 trace_id ,request_id 等。 ### 2、常见用法 #### (1)使用场景 在项目内经常会添加很多异步/定时任务,大部分情况下这些任务都是不受管理的 ,这样会造成数据丢失的情况。 > 例如启动了一个消费队列,消费队列内写入 mysql 数据库,在服务器关闭时,有可能 mysql 连接先关闭,但是消费队列还是有流量进入导致这部分流量写入失败。这个时候就可以用 context 来控制消费逻辑,确保在服务关闭时消费队列 与mysql同步关闭 。 #### (2)具体实现 在服务入口出定义一个全局的context,用于整个服务层面(区别与请求周期的 context)的组件控制。 ``` ctx,cancel = context.WithCancel(context.Background()) ``` > 控制循环退出 ``` for { select { case <-ctx.Done: //end return default: } //job } ``` > 控制定时任务 ``` ticker := time.NewTicker(time.Second) for{ select{ case <-ticker.C: //job case <- ctx.Done: ticker.Stop() // end return } } ``` 最后修改:2023 年 03 月 08 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏