2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
2026-06-07 12:14:39 +08:00
|
|
|
|
tags: [go, golang, go-principle, context]
|
|
|
|
|
|
create time: 2026-06-07 15:30
|
2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
# Context 底层原理
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 概述
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
本文从源码角度解析 Go `context` 包的四种实现(emptyCtx / cancelCtx / timerCtx / valueCtx),以及取消传播链和值传递链的底层机制。Context 是 Go 并发编程中控制取消和数据共享的核心工具,理解其实现能让你写出更健壮的并发程序。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!question] ❓ 思考
|
|
|
|
|
|
> 为什么 `WithValue` 派生的 context 层层嵌套形成链表,而不是用 map 存储?当父 context 被取消时,子 context 是如何级联收到信号的?
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 正文
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 一、Context 的类型体系
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TB
|
|
|
|
|
|
C["Context interface<br/>Deadline/Done/Err/Value"] --> empty["emptyCtx<br/>根 context"]
|
|
|
|
|
|
C --> cancel["cancelCtx<br/>可取消"]
|
|
|
|
|
|
C --> timer["timerCtx<br/>可取消 + 定时"]
|
|
|
|
|
|
C --> value["valueCtx<br/>键值对传递"]
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
timer -.嵌入.-> cancel
|
|
|
|
|
|
cancel -.嵌入.-> C
|
|
|
|
|
|
value -.嵌入.-> C
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
B["Background()"] --> empty
|
|
|
|
|
|
T["TODO()"] --> empty
|
|
|
|
|
|
WC["WithCancel(parent)"] --> cancel
|
|
|
|
|
|
WD["WithDeadline/Timeout(parent)"] --> timer
|
|
|
|
|
|
WV["WithValue(parent, key, val)"] --> value
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
style C fill:#e3f2fd
|
|
|
|
|
|
style empty fill:#e8f5e9
|
|
|
|
|
|
style cancel fill:#fff9c4
|
|
|
|
|
|
style timer fill:#fff3e0
|
|
|
|
|
|
style value fill:#fce4ec
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
四个结构体的核心职责:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
| 类型 | 可取消 | 有 Deadline | 存值 | 用途 |
|
|
|
|
|
|
|------|--------|-------------|------|------|
|
|
|
|
|
|
| `emptyCtx` | 否 | 否 | 否 | 根 context (Background/TODO) |
|
|
|
|
|
|
| `cancelCtx` | 是 | 否 | 否 | WithCancel 派生 |
|
|
|
|
|
|
| `timerCtx` | 是 | 是 | 否 | WithDeadline/Timeout 派生 |
|
|
|
|
|
|
| `valueCtx` | 否 | 否 | 是 | WithValue 派生 |
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 二、取消传播链:cancelCtx
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### 数据结构
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type cancelCtx struct {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
Context // 嵌入父 context
|
|
|
|
|
|
mu sync.Mutex // 保护以下字段
|
|
|
|
|
|
done atomic.Value // chan struct{},nil 或未关闭 → 未取消;已关闭 → 已取消
|
|
|
|
|
|
children map[canceler]struct{} // 子 canceler 集合
|
|
|
|
|
|
err error // 取消原因
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### Done 通道的懒汉创建
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func (c *cancelCtx) Done() <-chan struct{} {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
d := c.done.Load()
|
|
|
|
|
|
if d != nil { return d.(chan struct{}) }
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
d = c.done.Load() // 双重检查
|
|
|
|
|
|
if d == nil {
|
|
|
|
|
|
d = make(chan struct{})
|
|
|
|
|
|
c.done.Store(d)
|
|
|
|
|
|
}
|
|
|
|
|
|
return d.(chan struct{})
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
注意这个 channel 是**只读的**——只有父 context 关闭它,子 goroutine 通过 `select` 监听它来感知取消信号。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### 取消逻辑:递归级联
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
c.mu.Lock()
|
|
|
|
|
|
if c.err != nil { c.mu.Unlock(); return } // 已取消
|
|
|
|
|
|
c.err = err
|
|
|
|
|
|
d, _ := c.done.Load().(chan struct{})
|
|
|
|
|
|
if d == nil {
|
|
|
|
|
|
c.done.Store(closedchan) // 预创建的 closed chan
|
|
|
|
|
|
} else {
|
|
|
|
|
|
close(d) // 关闭通道,通知所有监听者
|
|
|
|
|
|
}
|
|
|
|
|
|
for child := range c.children {
|
|
|
|
|
|
child.cancel(false, err) // 递归取消子节点
|
|
|
|
|
|
}
|
|
|
|
|
|
c.children = nil
|
|
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
if removeFromParent {
|
|
|
|
|
|
removeChild(c.Context, c) // 从父节点移除自己
|
|
|
|
|
|
}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
取消流程:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart TD
|
|
|
|
|
|
P["父 cancelCtx"] --> C1["子 cancelCtx 1"]
|
|
|
|
|
|
P --> C2["子 cancelCtx 2"]
|
|
|
|
|
|
C1 --> C1a["孙 cancelCtx"]
|
|
|
|
|
|
C2 --> C2a["孙 valueCtx"]
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
style P fill:#ffebee
|
|
|
|
|
|
style C1 fill:#fff3e0
|
|
|
|
|
|
style C2 fill:#fff3e0
|
|
|
|
|
|
style C1a fill:#e8f5e9
|
|
|
|
|
|
style C2a fill:#e8f5e9
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
click P "触发 cancel()"
|
|
|
|
|
|
click C1 "级联取消"
|
|
|
|
|
|
click C2 "级联取消"
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
调用 `cancel()` 后:
|
|
|
|
|
|
1. 关闭自己的 `done` channel → 所有监听该 channel 的 goroutine 收到信号
|
|
|
|
|
|
2. 递归取消所有子节点
|
|
|
|
|
|
3. 将自己从父节点的 children 中移除
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 三、父子关联:propagateCancel
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func propagateCancel(parent Context, child canceler) {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
done := parent.Done()
|
|
|
|
|
|
if done == nil {
|
|
|
|
|
|
return // 父节点永远不会被取消
|
|
|
|
|
|
}
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-done:
|
|
|
|
|
|
child.cancel(false, parent.Err()) // 父已取消,子直接取消
|
|
|
|
|
|
return
|
|
|
|
|
|
default:
|
|
|
|
|
|
}
|
|
|
|
|
|
// 尝试从父提取 cancelCtx
|
|
|
|
|
|
if p, ok := parentCancelCtx(parent); ok {
|
|
|
|
|
|
p.mu.Lock()
|
|
|
|
|
|
if p.err != nil {
|
|
|
|
|
|
child.cancel(false, p.err)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if p.children == nil {
|
|
|
|
|
|
p.children = make(map[canceler]struct{})
|
|
|
|
|
|
}
|
|
|
|
|
|
p.children[child] = struct{}{}
|
|
|
|
|
|
}
|
|
|
|
|
|
p.mu.Unlock()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 父不是标准 cancelCtx,启动 goroutine 监控
|
|
|
|
|
|
atomic.AddInt32(&goroutines, +1)
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-parent.Done():
|
|
|
|
|
|
child.cancel(false, parent.Err())
|
|
|
|
|
|
case <-child.Done():
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
三种情形:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
| 父 context 情况 | 处理方式 |
|
|
|
|
|
|
|----------------|---------|
|
|
|
|
|
|
| `Done() == nil`(永远不取消) | 无需关联 |
|
|
|
|
|
|
| 能提取出 `cancelCtx` | 直接加入 children map |
|
|
|
|
|
|
| 不能提取 `cancelCtx`(如 valueCtx 链中的某层) | 起一个 goroutine 监控 |
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 四、值传递链:valueCtx
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### 数据结构
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
type valueCtx struct {
|
|
|
|
|
|
Context // 父 context
|
|
|
|
|
|
key, val interface{} // 当前层的键值对
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
每个 `valueCtx` 只存**一对**键值对,多层 `WithValue` 会形成嵌套链表:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart LR
|
|
|
|
|
|
V2["valueCtx2<br/>key2=val2"] --> V1["valueCtx1<br/>key1=val1"]
|
|
|
|
|
|
V1 --> E["emptyCtx<br/>Background"]
|
|
|
|
|
|
style V2 fill:#fce4ec
|
|
|
|
|
|
style V1 fill:#fff3e0
|
|
|
|
|
|
style E fill:#e8f5e9
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
查找过程:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func (c *valueCtx) Value(key interface{}) interface{} {
|
|
|
|
|
|
if c.key == key {
|
|
|
|
|
|
return c.val
|
|
|
|
|
|
}
|
|
|
|
|
|
return c.Context.Value(key) // 向上递归查找
|
|
|
|
|
|
}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!warning] ⚠️ 性能提示
|
|
|
|
|
|
> Value 查找是 O(n) 线性搜索(n = 嵌套层数)。过深的嵌套会影响性能,建议控制在合理范围内。同时,key 应避免使用容易碰撞的类型,推荐使用自定义不可比较类型。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 推荐的 key 定义方式
|
|
|
|
|
|
type ctxKey string
|
|
|
|
|
|
const myKey ctxKey = "my-key"
|
|
|
|
|
|
ctx := context.WithValue(ctx, myKey, value)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 五、TimerCtx:定时取消
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
type timerCtx struct {
|
|
|
|
|
|
cancelCtx
|
|
|
|
|
|
timer *time.Timer
|
|
|
|
|
|
deadline time.Time
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
`timerCtx` 嵌入了 `cancelCtx`,额外增加了一个定时器。在 `WithDeadline` 中:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
|
|
|
|
|
|
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
|
|
|
|
|
|
return WithCancel(parent) // 父 deadline 更早,直接返回 cancelCtx
|
|
|
|
|
|
}
|
|
|
|
|
|
c := &timerCtx{cancelCtx: newCancelCtx(parent), deadline: d}
|
|
|
|
|
|
propagateCancel(parent, c)
|
|
|
|
|
|
dur := time.Until(d)
|
|
|
|
|
|
if dur <= 0 {
|
|
|
|
|
|
c.cancel(true, DeadlineExceeded)
|
|
|
|
|
|
return c, func() { c.cancel(false, Canceled) }
|
|
|
|
|
|
}
|
|
|
|
|
|
c.timer = time.AfterFunc(dur, func() {
|
|
|
|
|
|
c.cancel(true, DeadlineExceeded)
|
|
|
|
|
|
})
|
|
|
|
|
|
return c, func() { c.cancel(true, Canceled) }
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 六、最佳实践
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!tip] 💡 技巧
|
|
|
|
|
|
> - **永远将 context 作为第一个参数**传入函数,命名为 `ctx`
|
|
|
|
|
|
> - **不要将 context 存入结构体**,只作为请求级别的传递工具
|
|
|
|
|
|
> - **不要用 context 传值做业务数据交换**,它是跨 API 边界的取消信号机制
|
|
|
|
|
|
> - **长生命周期对象不要用 context 传值**,会导致对象无法被 GC
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 小结
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
- Context 是接口,四种实现各司其职:空、取消、定时、传值
|
|
|
|
|
|
- 取消通过 `close(done channel)` + 递归遍历 children map 实现级联传播
|
|
|
|
|
|
- 值传递通过嵌套链表实现,查找为 O(n) 线性搜索
|
|
|
|
|
|
- `propagateCancel` 处理父子关联,支持三种场景
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 关联笔记
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
- [[hzh/GolangStar/Go语言进阶/Context]] — Context 的基础用法
|
|
|
|
|
|
- [[hzh/GolangStar/Go语言进阶/协程池]] — Context 在 worker pool 中的应用
|
|
|
|
|
|
- [[hzh/GolangStar/Go面试题库/Context面试题]] — Context 相关高频面试题
|