Golang Context 简介

前言

在写Golang程序调用各种第三方库的时候, 经常会传一个叫做Context的参数. 之前基本上见到接Context, 根本不管是干什么用的, 直接无脑context.Background().

但是, 传着传着就不免发生一些小疑问, 这个参数到底是干什么用的呢? 这么多库都在使用, 至少说明其是Golang中的一个共识, 一个基础元素. 除了context.Background()一定还有其他的值, 否则也不会作为参数来接收了.

用了这么久, 都不知道它的作用, 这实在有点说不过去了, 于是抽时间来好好研究研究contextGolang中的作用.

作用

Golang源码包中, context.go很贴心的给出来官网介绍的文章: https://blog.golang.org/context

Golang中, 协程被设计为非常方便且轻量的用户态线程, 鼓励我们使用协程来完成各种耗时的操作.

假设, 有一个处理请求Agoroutine, 其同时又启动了n个额外的goroutine来协助操作. 此时, 若请求 A被取消, 那么所有相关的资源都需要被释放, 所有处理该请求的goroutine都应该快速退出, 防止造成资源的浪费. 那么问题来了, parent是无法主动关闭子协程的, 如何通知子协程需要提前退出呢?

我想了想, 这不就是多协程之间的通信么, 这块我熟啊. 用锁, 用管道, 用什么都行, 只要将取消的消息群发给所有子协程就可以了, 随便列举几个方案:

  • 共享变量. 通过共享一个bool变量, 子协程定期检测变量值来判断是否需要退出
  • 管道. 子协程定期检测管道是否关闭来判断是否需要退出
  • 等等

没错了, Context就是干这个用的. 它提出来的主要目的, 就是提醒子协程该退出了. 而它实现的原理也是通过协程通信实现的, 只是在其上面封了一层, 以方便调用.

虽然Context在刚开始的时候只是为了封装子协程的退出提醒(只是我的猜测), 但是既然都加上下文了, 自然得带点上下文环境变量了. 其作用通过Context接口暴露的方法可窥见一二:

  • Deadline() (deadline time.Time, ok bool)
    • 返回工作的截止时间. (若没有, 则 ok=false)
  • Done() <-chan struct{}
    • 返回一个管道. 若从管道中可以读到数据, 说明任务被关闭了. (若不需要, 返回 nil)
    • 通常做法为在需要退出时将管道关闭.
  • Err() error
    • 返回任务被关闭的原因.
  • Value(key any) any
    • 返回上下文包含的环境变量

因此, Context其实只能够干两件事

  1. 通知子协程提前退出
  2. 携带环境变量

context 包

Golangcontext包除了测试文件, 仅contxt.go一个文件. 且, 将context.go文件中的注释和空行去掉之后, 整个文件代码仅318行(使用版本为 go 1.18), 称得上短小精悍了. 这里就不分析源码了, 感兴趣的自己看一下, 简单整理一些context包中包含的内容:

官方已实现的常用Context结构体, 均为私有结构体, 需要使用下方的方法生成, 官方仅实现了常用的几个实例, 这几个实例可组合使用, 若不能满足自己需求也可以自己实现:

名称 作用
emptyCtx 空的Context, 所有接口实现均为空实现. 既永远不会停止
cancelCtx 可以被主动取消的Context. parent调用特定方法进行取消
timerCtx 定时器, 在指定时间后取消
valueCtx 永远不会停止, 用来传递上下文环境变量

context包中提供的公共方法, 我们通常调用的就是这些了, 可以看到, 官方提供的生成方法, 都是基于一个已有的context基础上进行生成, 也就是说我们在使用的时候可以进行组合, 将前一个生成的结果作为参数来生成下一个, 以获得拥有多种功能的context, 这个设计还是很巧妙的:

类型 名称 作用
方法 Background 返回一个emptyCtx实例
TODO 返回一个emptyCtx实例.
官方推荐是, 当不知道应该用哪个的时候, 临时使用TODO
WithCancel 在一个context的基础上, 生成一个可以取消的context.
两个context均可以执行取消的操作, 取决于哪个先发生
WithDeadline 在一个context基础上, 生成一个特定时间关闭的context
WithTimeout 在一个context基础上, 生成一个特定时长关闭的context
WithValue 在一个context基础上, 生成一个拥有指定k-vcontext
变量 Canceled 定义好的取消文案
DeadlineExceeded 定义好的取消文案. 时间到了

最后

最后, 在查看了Context内容之后, 有些个人的小疑问.

1. Context接口设计的过于臃肿

其实, Context并不需要保存k-v, 应用程序总是可以通过自己的方式将上下文在多个协程之间共享的.

并且, DeadlineDone方法是否有些臃肿? 其实子协程仅仅需要知道是否退出即可, 甚至于我觉得Context只需要实现这样一个方法就可以了:

IsCancel() (cancel bool, e err)

十分简洁, 只需要告诉子协程是否退出及原因不就可以了么?

当然, 也可能官方是出于其他我没有想到的原因考虑.

2. parent只能通知子协程退出, 但子协程什么时候退出parent是不知道的

如题, parent通过Context通知子协程退出后, 对于子协程是否退出是没有感知的. 现实中是否有这样的场景暂时没有想到, 不过应该会有吧.

订阅评论
提醒
guest
4 评论
最新
最旧
内联反馈
查看所有评论
4
0
希望看到您的想法,请发表评论。x