在Go语言的标准库中,context
包是一个重要而强大的工具,用于在并发环境中实现优雅的上下文传递。它为我们提供了一种简洁而灵活的方式,用于在程序的不同模块之间传递请求范围的值、控制并发操作、以及处理超时和取消操作等。了解context
包的内部实现细节,将使我们更好地理解其工作原理,并能够更好地应用于我们的实际项目中。
这里着重说源码,后续会开一个小篇章说说context的使用。
context ?
context自Go1.7版本之后被引入标准库,中文翻译是“上下文”,是专门用来简化对于处理单个请求、多个goroutine之间数据共享、信号取消、超时处理等相关操作的,更加准确地说,它其实就是goroutine的上下文,记录了goroutine的运行状态、环境、现场等相关信息。也正是因为context的引入,我们可以在很多的标准库 、 项目中看到接口中设置了context参数(比如database/sql)。
可以从另外一个角度去理解context。从操作系统角度看,线程/进程进行切换的时候,需要先保存当前的寄存器和栈指针,然后载入下一个进程/线程需要的寄存器和栈,此时,寄存器和栈就是进程/线程的context。在最后面补充了一些小的脑洞提问,有兴趣也可以看看。
在由Go编写的服务器中,每个请求都在自己的goroutine中处理。而处理请求的程序通常会启动其他goroutine来访问后端,就好像链接数据库和进行RPC请求。负责处理请求的goroutine集合通常也是需要访问特定请求的值,当请求被取消或者超时的时候,负责处理该请求的所有goroutine都应该快速退出,便于系统回收资源。
基于上面的模型,在很多场景下,一个请求会衍生出很多协程,而这些协程都有有关联关系的:他们之间共享全局变量、有共同的deadline、可以同时被关闭。在Go里面,协程的关闭一般会用channel + select的方式来控制,但是在这个场景下就会比较麻烦,这时,context的出现就显得十分必要跟优雅了。
源码分析(1.16.3)
既然了解了context产生的背景以及场景,接下我们就要了解下context的原理了。(本地brew安装的Go,更新的时候升级到了比较高的版本,但是不妨碍我们分析)
Go的context包其实就是context.go这个文件,总共500来行代码,其中大篇幅都是注释。
结构体 、 接口 的类图如下:
一个个看其实现。
Context 接口实现
type Context interface {
// returns the time when work done on behalf of this context should be canceled.
// 返回 context 是否被取消以及其deadline时间
Deadline() (deadline time.Time , ok bool)
// 当 context 被取消 或者到了deadline ,会返回一个关闭的channel
// 返回的 channel 是一个只读的channel
Done