文章目录
- 1. 概述
-
- 1.1 什么是 Context
- 1.2 设计原理
- 1.3 使用场景
- 1.4 Context 分类
-
- 核心:Context接口
- 2. 源码解读
-
- 4个实现
-
- emptyCtx
-
- TODO 和 Background
- cancelCtx
-
- WithCancel
-
- cancelCtx.propagateCancel 构建父子关联
- parentCancelCtx 获取父上下文中的内嵌cancelCtx
- cancel
- timerCtx
-
- WithTimeout 和 WithDeadline
-
- timerCtx.cancel
- WithValue
- 3. 总结
- 4. 参考
1. 概述
基于版本: go1.22.3/src/context/context.go
1.1 什么是 Context
上下文 context.Context在Go 语言中用来设置截止日期、同步信号,传递请求相关值的结构体。上下文与 Goroutine 有比较密切的关系,是 Go 语言中独特的设计,在其他编程语言中我们很少见到类似的概念。
主要用于超时控制和多Goroutine间的数据传递。
看一下官方定义
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. When a Context is canceled, all Contexts derived from it are also canceled.
-
Package context defines the Context type:Go语言中的context包定义了一个名为Context的类型。
-
which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes:Context类型用于在API边界之间以及不同进程之间传递诸如截止时间(deadlines)、取消信号(cancellation signals)和其他请求作用域内的值。
-
Incoming requests to a server should create a Context:当服务器接收到一个请求时,应该创建一个Context。
-
and outgoing calls to servers should accept a Context:当服务器向外发起调用时,应该接受一个Context。
-
The chain of function calls between them must propagate the Context:在这些函数调用链中,必须传递Context。
-
optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue:可以选择用WithCancel、WithDeadline、WithTimeout或WithValue方法创建的派生Context替换原有的Context。
-
When a Context is canceled, all Contexts derived from it are also canceled:当一个Context被取消时,所有从它派生出来的Context也会被取消。
简而言之,context包和Context类型在Go语言中用于控制请求的生命周期,包括传递截止时间、取消信号等信息,并确保这些信息能够在服务器之间的函数调用链中传递。当需要取消请求时,所有相关的Context都会被取消,这样可以优雅地终止请求处理。
1.2 设计原理
因为context.Context主要作用就是进行超时控制,然后外部程序监听到超时后就可以停止执行任务,取消 Goroutine。
网上有很多用 Context 来取消 Goroutine 的字眼,初学者(比如笔者)可能误会,以为 Context 可以直接取消 Goroutine。
实际,Context 只是完成了一个信号的传递,具体的取消逻辑需要由程序自己监听这个信号,然后手动处理。
Go 语言中的 Context 通过构建一颗 Context 树,从而将没有层级的 Goroutine 关联起来。
在超时或者手动取消的时候信号都会从最顶层的 Goroutine 一层一层传递到最下层。这样该 Context 关联的所有 Goroutine 都能收到信号,然后进入自定义的退出逻辑。
比如这里手动取消了 ctxB1,然后 ctxB1的两个子ctx(C1和C2)也会收到取消信号,这样3个Goroutine都能收到取消信号进行退出了。
1.3 使用场景
最常见的就是 后台 HTTP/RPC Server。
在 Go 的 server 里,通常每来一个请求都会启动若干个 goroutine 同时工作:有些去数据库拿数据,有些调用下游接口获取相关数据,具体如下图:
而客户端一般不会无限制的等待,都会被请求设定超时时间,比如100ms。
比如这里GoroutineA消耗80ms,GoroutineB3消耗30ms,已经超时了,那么后续的GoroutineCDEF都没必要执行了,客户端已经超时返回了,服务端就算计算出结果也没有任何意义了。
所以这里就可以使用 Context 来在多个 Goroutine 之间进行超时信号传递。
同时引入超时控制后有两个好处:
- 1)客户端可以快速返回,提升用户体验
- 2)服务端可以减少无效的计算
1.4 Context 分类
Context 在 Go 1.7 版本引入标准库中,主要内容可以概括为:
- 1 个接口
- Context
- 4 种实现
- emptyCtx
- cancelCtx
- timerCtx
- valueCtx
- 6 个方法
- Background
- TODO
- WithCancel
- WithDeadline
- WithTimeout
- WithValue
核心:Context接口
Context 它是一个接口,定义了几个方法:
type Context interface {
// 如果返回 ok==false 则表示没有设置Deadline时间
Deadline() (deadline time.Time, ok bool)
// Done():返回一个只读chan,如果可以从该 chan 中读取到数据,则说明 ctx 被取消了
Done() <-chan struct{
}
// 如果ctx没有被取消,返回nil;如果是,则返回相应的原因(错误类型)
Err() error
// 用于储存一些键值对。要注意使用类型断言。
Value(key interface{
}) interface{
}
}
2. 源码解读
4个实现
context 包的核心是 context.Context 接口,另外有四个 struct 实现了 Context 接口,分别是
- emptyCtx,
- cancelCtx
- timerCtx
- valueCtx,
其中 emptyCtx 是一个默认的空结构体,其余三个都是在其基础上添加了各自功能的实现,针对 emptyCtx ,context 包中暴露了两个方法 Background()
和 TODO()
去创建一个空的 emptyCtx
而针对后面三种具体的 struct ,context 包总共暴露了四个方法去产生对应的 struct, 他们分别是: WithCancel(), WithDeadLine(), WithTimeout(), WithValue(),对应关系如下:
emptyCtx
// emptyCtx 过空方法实现了 context.Context 接口,它没有任何功能。
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{
} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{
}) interface{
} {
return nil
}
TODO 和 Background
TODO 和 Background 方法用来返回一个 emptyCtx
类型,他们在实现上都一样:
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
从源代码来看,context.Background
和 context.TODO
和也只是互为别名,没有太大的差别,只是在使用和语义上稍有不同:
context.Background
是上下文的默认值,所有其他的上下文都应该从它衍生出来;context.TODO
应该仅在不确定应该使用哪种上下文时使用;
在多数情况下,如果当前函数没有上下文作为入参,我们都会使用 context.Background 作为起始的上下文向下传递。
cancelCtx
这是一个带 cancel 功能的 context。
type cancelCtx struct {
Context
mu sync.Mutex // 用于同步
done chan struct{
} // 会在 Done 中返回
children map[canceler]struct{
} // 子上下文列表,done 被关闭后,会遍历这个 map,关闭所有的子上下文
err error // 关闭 chan 产生的异常,在初始化时会被赋值使不为空