# Golang Context 学习笔记

本文基于 Go 1.21 源码分析

# 📋 目录


# 前言

Context 是 Golang 中用于处理并发控制的经典工具,主要解决以下三个问题:

  1. 并发协调:在多个 goroutine 之间传递取消信号
  2. 生命周期管理:控制 goroutine 的启动和终止,统一超时、截止时间、取消等控制逻辑
  3. 数据传递:在调用链中传递请求范围的数据,跨层级传递共享数据

本文深入分析 context 的四种实现类型及其优化细节。


# Go 1.21 Context 的重要更新

# 核心变更

emptyCtx 类型改变

  • Go 1.20 及之前type emptyCtx int
  • Go 1.21 开始type emptyCtx struct{}

这是一个重要的底层实现变更,虽然对外部 API 无影响,但改变了内部设计哲学。

# 为什么要改?

旧设计(int 类型)的理由

// Go 1.20 源码注释
// An emptyCtx is never canceled, has no values, and has no deadline.
// It is not struct{}, since vars of this type must have distinct addresses.
type emptyCtx int
  • 使用 int 是为了让 backgroundtodo 两个实例有不同的内存地址
  • struct{} 类型的所有实例在 Go 中可能共享同一个地址(零字节优化)

新设计(struct {} 类型)的考量

// Go 1.21+ 源码
type emptyCtx struct{}
  • Go 团队重新评估后认为区分 backgroundtodo 的地址并非必要
  • struct{} 更符合语义:一个真正 "空" 的上下文
  • 简化了类型系统,更加直观

# Context 核心接口

# 接口定义

type Context interface {
    Deadline() (deadline time.Time, ok bool)  // 返回过期时间
    Done() <-chan struct{}                     // 返回取消信号通道
    Err() error                                // 返回取消原因
    Value(key any) any                         // 获取存储的值
}

# 四种核心实现

类型 用途 创建函数
emptyCtx 根上下文,永不取消 Background() , TODO()
cancelCtx 可手动取消 WithCancel()
timerCtx 定时自动取消 WithDeadline() , WithTimeout()
valueCtx 携带键值对数据 WithValue()

# 标准错误类型

// 主动取消错误
var Canceled = errors.New("context canceled")
// 超时取消错误 (实现了 net.Error 接口)
var DeadlineExceeded error = deadlineExceededError{}
type deadlineExceededError struct{}
func (deadlineExceededError) Error() string   { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool   { return true }
func (deadlineExceededError) Temporary() bool { return true }

理解要点

  • Canceled :调用 cancel 函数时返回
  • DeadlineExceeded :达到截止时间时返回
  • Temporary() 返回 true 表示错误是临时的,可以重试

# emptyCtx:空上下文

# 数据结构

//emptyCtx 是一个空结构体,不占用任何实际内存
type emptyCtx struct{}
// 四个接口方法都是空实现
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
    return  // 返回零值 time.Time 和 false
}
func (*emptyCtx) Done() <-chan struct{} {
    return nil  // 返回 nil channel
}
func (*emptyCtx) Err() error {
    return nil
}
func (*emptyCtx) Value(key any) any {
    return nil
}
func (*emptyCtx) String() string {
    // 用于调试,区分 background 和 todo
    switch e {
    case background:
        return "context.Background"
    case todo:
        return "context.TODO"
    }
    return "unknown empty Context"
}

# 特点分析

emptyCtx 是最简单的 context 实现:

  • 它的所有方法都返回空值
  • 它永远不会被取消
  • 没有任何 key/value
  • 就是 一个 “纯空壳” 的 Context
特性 说明 影响
类型 底层是 struct{} 类型 零字节,但编译器可能对齐到 1 字节
过期 Deadline 返回零值和 false 永不过期
取消 Done 返回 nil 读写 nil channel 都会永久阻塞
错误 Err 永远返回 nil 无错误信息
存储 Value 永远返回 nil 无数据存储能力

# 内存布局对比

// Go 1.20 及之前
type emptyCtx int
//- 占用 8 字节(64 位系统)
//- background 和 todo 有不同地址
// Go 1.21+
type emptyCtx struct{}
//- 占用 0 字节(理论上)
//- 实际可能对齐到 1 字节
//- background 和 todo 可能共享地址(但无影响)

为什么地址相同也没问题?

  • backgroundtodo 只是语义上的区分
  • 它们的行为完全相同
  • 代码中通过函数名区分意图,而非通过地址比较

# 使用场景

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)
func Background() Context { 
    return background 
}
func TODO() Context { 
    return todo 
}

两个单例实例的区别

函数 使用场景 语义
Background() main 函数、初始化、测试的根上下文 "这是一个明确的起点"
TODO() 暂时不确定用什么上下文 "这里需要补充合适的 context"

为什么返回相同类型却用两个函数?

  1. 语义区分:代码审查时能看出意图差异
  2. 静态分析友好:工具可以检测并提示 TODO 的使用
  3. 文档作用TODO() 提醒开发者后续需要替换
  4. 后续扩展:未来版本可能有不同实现

实际使用示例

// ✅ 正确:明确的根上下文
func main() {
    ctx := context.Background()
    server.Run(ctx)
}
// ✅ 正确:暂时占位
func processRequest(req *Request) error {
    // TODO: 应该从请求中提取 context
    ctx := context.TODO()
    return doWork(ctx, req)
}
// ❌ 错误:滥用 TODO
func (s *Service) Start() {
    ctx := context.TODO()  // 应该用 Background
    s.run(ctx)
}

# cancelCtx:可取消上下文

# 数据结构

type cancelCtx struct {
    Context                           // 父 context
    mu       sync.Mutex               // 保护以下字段
    done     atomic.Value             // 类型为 chan struct {},懒加载
    children map[canceler]struct{}    // 子 context 集合
    err      error                    // 第一次取消时设置的错误
    cause    error                    // 取消的根本原因 (Go 1.20+)
}
type canceler interface {
    cancel(removeFromParent bool, err, cause error)
    Done() <-chan struct{}
}

# 设计亮点

  1. atomic.Value + Mutex 双重保护

    // 快速路径(无锁):读取 done channel
    d := c.done.Load()
    // 慢速路径(加锁):初始化 done channel
    c.mu.Lock()
    c.done.Store(make(chan struct{}))
    c.mu.Unlock()
  2. children map 的高效管理

    // 使用空 struct {} 作为值,零内存占用
    children map[canceler]struct{}
    // 添加子节点
    p.children[child] = struct{}{}
    // 批量取消,O (N) 复杂度
    for child := range c.children {
        child.cancel(false, err, cause)
    }
  3. cause 字段的引入

    // 区分直接原因和根本原因
    c.err = Canceled              // 直接原因:被取消
    c.cause = ErrTimeout          // 根本原因:上游超时

# Done 方法:懒加载 + Double Check

func (c *cancelCtx) Done() <-chan struct{} {
    // 第一次检查:无锁快速路径
    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{})
}

性能分析

// 场景 1:已初始化的 context (90% 的情况)
// 执行路径:Load → 类型断言 → 返回
// 耗时:~1-2ns (无锁,CPU cache 友好)
// 场景 2:未初始化的 context (首次调用)
// 执行路径:Load → Lock → Load → make → Store → Unlock → 返回
// 耗时:~50-100ns (含锁开销)
// 场景 3:并发初始化 (极少发生)
// 第一个协程:完整慢速路径,创建 channel
// 后续协程:在锁外等待 → 第二次检查成功 → 返回已创建的 channel

为什么用 atomic.Value 而不是 sync.RWMutex?

方案 Load 开销 并发性能 适用场景
atomic.Value ~1ns 无锁,完美扩展 读多写少(done 只创建一次)
sync.RWMutex ~20ns RLock 仍有竞争 读写都频繁
sync.Mutex ~50ns 读也需要加锁 写操作为主

done channel 的特点:

  • 创建一次,读取多次(典型的读多写少)
  • 创建后永不改变(非常适合 atomic.Value)
  • 高并发读取(select 语句会频繁调用 Done ())

# WithCancel 创建流程

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
  // 返回 context 和取消函数
	return c, func() { c.cancel(true, Canceled, nil) }
}
func withCancel(parent Context) *cancelCtx {
  // 参数校验
	if parent == nil {
		panic("cannot create context from nil parent")
	}
  // 创建 cancelCtx
	c := &cancelCtx{}
  // 核心:建立父子关系,传播取消信号
	c.propagateCancel(parent, c)
	return c
}

使用示例

// 创建可取消的 context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()  // 确保资源释放
// 启动多个 goroutine
for i := 0; i < 10; i++ {
    go func(id int) {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d: canceled\n", id)
            return
        case <-doWork():
            fmt.Printf("Worker %d: done\n", id)
        }
    }(i)
}
// 在某个条件下取消所有 goroutine
if someCondition {
    cancel()  // 一次调用,取消所有子 goroutine
}

# propagateCancel:取消信号传播机制

这是 cancelCtx 最核心的方法,决定了如何响应父 context 的取消:

//propagateCancel 设置父子 context 之间的取消传播关系,确保父 context 被取消时子 context 也会被取消
// 参数:
//   - parent: 父级 Context,当它被取消时会触发子级取消
//   - child: 需要在父级取消时一同被取消的 canceler(子级)
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
	// 将当前 cancelCtx 的父 context 设置为传入的 parent
	c.Context = parent
	// 获取父 context 的 Done channel
	done := parent.Done()
	// 如果父 context 永远不会被取消(Done 为 nil),则直接返回
	if done == nil {
		return // parent is never canceled
	}
	// 检查父 context 是否已经被取消
	select {
	case <-done:
		// 父 context 已经被取消,立即取消子 context
		// 使用 false 作为 removeFromParent 参数是因为父 context 已经取消,
		// 不需要从父 context 的 children 中移除子 context
		child.cancel(false, parent.Err(), Cause(parent))
		return
	default:
	}
	// 尝试获取父 context 对应的 *cancelCtx
	// 如果成功,则可以直接操作其 children 映射来建立父子关系
	if p, ok := parentCancelCtx(parent); ok {
		//parent 是一个 *cancelCtx 或者派生自 *cancelCtx
		p.mu.Lock()
		// 再次检查父 context 是否已经被取消(双重检查锁定模式)
		if err := p.err.Load(); err != nil {
			// 父 context 已经被取消,立即取消子 context
			child.cancel(false, err.(error), p.cause)
		} else {
			// 父 context 还未被取消,在其 children 映射中添加子 context
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
		return
	}
	// 检查父 context 是否实现了 afterFuncer 接口(具有 AfterFunc 方法)
	if a, ok := parent.(afterFuncer); ok {
		// 父 context 实现了 AfterFunc 方法
		c.mu.Lock()
		// 注册一个回调函数,当父 context 被取消时执行此函数来取消子 context
		stop := a.AfterFunc(func() {
			child.cancel(false, parent.Err(), Cause(parent))
		})
		// 将当前 context 包装成 stopCtx,保存 stop 函数以便后续清理
		c.Context = stopCtx{
			Context: parent,
			stop:    stop,
		}
		c.mu.Unlock()
		return
	}
	// 上述优化路径都不适用时,启动一个新的 goroutine 来监听父 context 的取消信号
	// 增加 goroutine 计数器,用于测试目的
	goroutines.Add(1)
	go func() {
		select {
		case <-parent.Done():
			// 父 context 被取消时,取消子 context
			child.cancel(false, parent.Err(), Cause(parent))
		case <-child.Done():
			// 子 context 自己被取消时,什么都不做(退出 goroutine)
		}
	}()
}

三种传播策略详解

场景 策略 优点 缺点 适用场景
父是 emptyCtx 直接返回 无任何开销 - Background/TODO 作为父
父是 cancelCtx 加入 children map ・取消时 O (1) 查找
・无额外协程
・内存高效
需要加锁 标准库 context(99%)
父是自定义类型 启动监听协程 ・通用性强
・支持任意 Context 实现
・每个子一个协程
・内存和调度开销
自定义 Context 接口实现

策略选择的性能影响

// 场景 1:使用标准 context(高效)
parent := context.Background()
ctx1, _ := context.WithCancel(parent)
ctx2, _ := context.WithCancel(ctx1)
ctx3, _ := context.WithCancel(ctx2)
// 资源消耗:3 个 cancelCtx 结构体
// 取消开销:O (N),N = 子节点数量
// 场景 2:使用自定义 context(低效)
type myContext struct {
    context.Context
}
parent := &myContext{context.Background()}
ctx1, _ := context.WithCancel(parent)  // 启动 1 个监听协程
ctx2, _ := context.WithCancel(parent)  // 启动 1 个监听协程
ctx3, _ := context.WithCancel(parent)  // 启动 1 个监听协程
// 资源消耗:3 个 cancelCtx + 3 个 goroutine
// 取消开销:channel 通信 + 协程调度

# parentCancelCtx:类型识别技巧

func parentCancelCtx(parent Context) (*cancelCtx, bool) {
    // 快速检查:parent.Done () 返回 nil 或已关闭
    done := parent.Done()
    if done == closedchan || done == nil {
        return nil, false
    }
    
    // 利用特殊 key 识别 cancelCtx
    p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
    if !ok {
        return nil, false
    }
    
    // 验证 done channel 一致性
    pdone, _ := p.done.Load().(chan struct{})
    if pdone != done {
        return nil, false
    }
    return p, true
}
// 包私有变量,用作识别协议的 key
var cancelCtxKey int
//cancelCtx 的 Value 方法实现类型识别协议
func (c *cancelCtx) Value(key any) any {
    if key == &cancelCtxKey {
        return c  // 特殊 key 返回自身
    }
    return value(c.Context, key)  // 其他 key 向上查找
}

设计巧妙之处

  1. 避免反射

    // ❌ 低效方案(使用反射)
    if reflect.TypeOf(parent).String() == "*context.cancelCtx" {
        return parent.(*cancelCtx), true
    }
    // ✅ 高效方案(使用协议)
    if p, ok := parent.Value(&cancelCtxKey).(*cancelCtx); ok {
        return p, true
    }
  2. 利用包私有变量地址

    var cancelCtxKey int
    //cancelCtxKey 的地址是包私有的,外部无法伪造
    // &cancelCtxKey 作为 key,不会与用户的 key 冲突
  3. done channel 一致性检查

    // 场景:自定义 context 可能伪造 Value 方法
    type fakeCtx struct {
        context.Context
    }
    func (f *fakeCtx) Value(key any) any {
        if key == &cancelCtxKey {
            return &cancelCtx{}  // 返回假的 cancelCtx
        }
        return f.Context.Value(key)
    }
    //done channel 检查防止这种伪造
    pdone, _ := p.done.Load().(chan struct{})
    if pdone != done {
        return nil, false  // 检测到不一致,拒绝
    }

# cancel 方法:级联取消

func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
    // 参数校验
    if err == nil {
        panic("context: internal error: missing cancel error")
    }
    
    c.mu.Lock()
    
    // 幂等性检查:已经取消过
    if c.err != nil {
        c.mu.Unlock()
        return  // 重复调用 cancel 是安全的
    }
    
    // 设置错误信息
    c.err = err
    if cause == nil {
        cause = err
    }
    c.cause = cause
    
    // 关闭 done channel
    d, _ := c.done.Load().(chan struct{})
    if d == nil {
        // 未初始化:存储预先关闭的 channel
        c.done.Store(closedchan)
    } else {
        // 已初始化:关闭 channel
        close(d)
    }
    
    // 递归取消所有子 context
    for child := range c.children {
        // 注意:removeFromParent=false,避免子节点回调父节点
        child.cancel(false, err, cause)
    }
    c.children = nil  // 释放 map 内存
    c.mu.Unlock()
    // 从父 context 移除自己
    if removeFromParent {
        removeChild(c.Context, c)
    }
}

关键设计决策

  1. 幂等性保证

    cancel, cancel := context.WithCancel(parent)
    cancel()  // 第一次调用
    cancel()  // 第二次调用:安全,直接返回
    cancel()  // 第三次调用:安全,直接返回
  2. 为什么先关闭 channel 再取消子节点?

    // 关闭顺序很重要
    close(d)              // ← 先关闭,确保监听者立即收到信号
    for child := range c.children {
        child.cancel(...) // ← 后取消,可能耗时较长
    }
    // 这样保证:
    // 1. select {case <-ctx.Done ()} 立即返回
    // 2. 子节点取消不阻塞当前节点的通知
  3. closedchan 全局单例

    var closedchan = make(chan struct{})
    func init() {
        close(closedchan)
    }
    // 优点:
    //- 避免每次都 make + close,节省内存分配
    //- 所有未初始化就取消的 context 共享同一个已关闭 channel
    //- select {case <-closedchan} 立即返回
  4. removeFromParent 参数的妙用

    // 场景 1:用户主动调用 cancel ()
    cancel()
    // → c.cancel(true, Canceled, nil)
    // → removeChild(parent, c)
    // 原因:需要从父的 children map 中删除自己
    // 场景 2:父节点取消触发子节点取消
    parent.cancel(...)
    // → for child := range parent.children {
    //       child.cancel(false, err, cause)  // removeFromParent=false
    //   }
    // → parent.children = nil
    // 原因:父节点会清空整个 children map,无需逐个删除
  5. 设置 c.children = nil 的作用

    // 释放 map 占用的内存
    c.children = nil
    // 效果:
    // 1. 允许 GC 回收 map 本身
    // 2. 允许 GC 回收子 context(如果没有其他引用)
    // 3. 防止内存泄漏

# removeChild:从父节点移除

func removeChild(parent Context, child canceler) {
    // 类型检查:只有 cancelCtx 才有 children
    p, ok := parentCancelCtx(parent)
    if !ok {
        return  // 父不是 cancelCtx,无需操作
    }
    
    p.mu.Lock()
    if p.children != nil {
        delete(p.children, child)
    }
    p.mu.Unlock()
}

调用场景分析

// 场景 1:子 context 先于父 context 取消
ctx, cancel := context.WithCancel(parent)
cancel()
// → c.cancel(true, ...)
// → removeChild(parent, c)
// → delete(parent.children, c)
// 场景 2:父 context 取消导致子 context 取消
parentCancel()
// → parent.cancel(...)
// → for child := range parent.children {
//       child.cancel (false, ...)  // 不调用 removeChild
//   }
// → parent.children = nil  // 直接清空整个 map

# WithCancelCause:指定取消原因

func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {
	c := withCancel(parent)
	return c, func(cause error) { c.cancel(true, Canceled, cause) }
}
// 获取取消的根本原因
func Cause(c Context) error {
	if cc, ok := c.Value(&cancelCtxKey).(*cancelCtx); ok {
		cc.mu.Lock()
		cause := cc.cause
		cc.mu.Unlock()
		if cause != nil {
			return cause
		}
	}
	return c.Err()
}

err vs cause 的区别

ctx, cancel := context.WithCancelCause(context.Background())
// 取消并指定原因
cancel(fmt.Errorf("database connection failed: %w", err))
// 获取错误信息
fmt.Println(ctx.Err())           // 输出:context canceled
fmt.Println(context.Cause(ctx))  // 输出:database connection failed: ...
字段 含义 用途
err 直接原因 始终是 CanceledDeadlineExceeded
cause 根本原因 业务层面的详细错误信息

实际应用场景

// 场景 1:HTTP 请求处理
func handleRequest(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithCancelCause(r.Context())
    defer cancel(nil)
    
    result := make(chan string, 1)
    go func() {
        data, err := fetchData(ctx)
        if err != nil {
            cancel(fmt.Errorf("fetch failed: %w", err))
            return
        }
        result <- data
    }()
    
    select {
    case data := <-result:
        fmt.Fprintf(w, "Result: %s", data)
    case <-ctx.Done():
        cause := context.Cause(ctx)
        log.Printf("Request canceled: %v", cause)
        http.Error(w, "Request failed", 500)
    }
}
// 场景 2:分布式追踪
func processWithTracing(ctx context.Context) error {
    ctx, cancel := context.WithCancelCause(ctx)
    defer cancel(nil)
    
    if err := step1(ctx); err != nil {
        cancel(fmt.Errorf("step1 failed: %w", err))
        return context.Cause(ctx)
    }
    
    if err := step2(ctx); err != nil {
        cancel(fmt.Errorf("step2 failed: %w", err))
        return context.Cause(ctx)
    }
    
    return nil
}

# timerCtx:定时取消上下文

# 数据结构

type timerCtx struct {
    cancelCtx                      // 内嵌 cancelCtx,复用其能力
    timer    *time.Timer           // 定时器
    deadline time.Time             // 截止时间
}

设计特点

  • 通过内嵌 cancelCtx 复用取消逻辑
  • 额外增加定时器和截止时间管理
  • 支持手动取消和自动超时两种方式

# Deadline 方法

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
    return c.deadline, true
}

这是唯一真正实现 Deadline() 接口的 context 类型:

  • emptyCtx :返回零值和 false
  • cancelCtx :返回零值和 false
  • valueCtx :向上查找父 context
  • timerCtx:返回实际截止时间和 true ✓

# WithDeadline:带截止时间的 Context

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	return WithDeadlineCause(parent, d, nil)
}
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
  // 参数校验
	if parent == nil {
		panic("cannot create context from nil parent")
	}
  // 父截止时间更早,直接返回 cancelCtx
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// 无需设置新的定时器,父结点超时会自动传播
		return WithCancel(parent)
	}
  // 创建 timerCtx
	c := &timerCtx{
		deadline: d,
	}
	c.cancelCtx.propagateCancel(parent, c)
  // 计算剩余时间
	dur := time.Until(d)
	if dur <= 0 {
    // 已过期,立即取消
		c.cancel(true, DeadlineExceeded, cause) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err.Load() == nil {
    // 启动定时器
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded, cause)
		})
	}
	return c, func() { c.cancel(true, Canceled, nil) }
}

两个关键优化

  1. 父截止时间检查

    // 场景:parent 5 秒后超时,子 context 请求 10 秒超时
    parent, _ := context.WithTimeout(ctx, 5*time.Second)
    child, _ := context.WithTimeout(parent, 10*time.Second)
    // 实际:child 是一个 cancelCtx,不是 timerCtx
    // 原因:parent 会在 5 秒后超时并取消 child
    // 优点:节省一个 timer 资源
  2. 已过期检查

    // 场景:请求过去的时间点
    deadline := time.Now().Add(-1 * time.Second)
    ctx, cancel := context.WithDeadline(parent, deadline)
    // 效果:ctx.Err () 立即返回 DeadlineExceeded
    // 优点:不创建 timer,不启动 goroutine

性能对比

场景 是否创建 timer 资源消耗
正常情况 timer + timerCtx 结构体
父截止时间更早 仅 cancelCtx 结构体
请求过期时间 仅 timerCtx 结构体(已取消)

# WithTimeout:便捷函数

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

常见用法

// HTTP 请求超时
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://example.com", nil)
resp, err := http.DefaultClient.Do(req)
// 数据库查询超时
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = ?", userID)
// RPC 调用超时
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
reply, err := client.Call(ctx, "Service.Method", args)
// 文件操作超时
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
data, err := os.ReadFile(ctx, "/path/to/large/file")

# cancel 方法:增强版

func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
    // 复用 cancelCtx 的取消逻辑
    c.cancelCtx.cancel(false, err, cause)
    
    // 从父节点移除
    if removeFromParent {
        removeChild(c.cancelCtx.Context, c)
    }
    
    // 停止定时器,防止资源泄漏
    c.mu.Lock()
    if c.timer != nil {
        c.timer.Stop()
        c.timer = nil
    }
    c.mu.Unlock()
}

为什么必须停止 timer?

// ❌ 错误示例:忘记 cancel
func badExample() {
    for i := 0; i < 10000; i++ {
        ctx, _ := context.WithTimeout(parent, time.Hour)
        // 忘记调用 cancel
        doQuickWork(ctx) // 1 秒内完成
        //timer 还会继续运行 59 分 59 秒
    }
    // 结果:10000 个 timer 在后台运行,内存和 CPU 泄漏
}
// ✅ 正确示例
func goodExample() {
    for i := 0; i < 10000; i++ {
        ctx, cancel := context.WithTimeout(parent, time.Hour)
        defer cancel() // 或在合适时机调用
        doQuickWork(ctx)
    }
    //timer 立即停止,资源释放
}

资源泄漏分析

// 每个 timer 的资源消耗
type Timer struct {
    C <-chan Time      // 至少 8 字节
    r runtimeTimer     // 约 88 字节
}
// 10000 个 timer
// 内存:~960 KB
// CPU:定期唤醒检查是否到期
// 影响:GC 压力增大,调度延迟增加

正确的使用模式

// 模式 1:defer cancel(最推荐)
func process() error {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    return doWork(ctx)
}
// 模式 2:显式 cancel
func process() error {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    err := doWork(ctx)
    cancel()  // 立即释放
    return err
}
// 模式 3:goroutine 中 cancel
func processAsync() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    go func() {
        defer cancel()
        doWork(ctx)
    }()
}

# timer.Stop () 的行为

//timer.Stop () 的返回值
stopped := c.timer.Stop()
if stopped {
    //timer 还未触发,成功停止
    // 定时器的回调函数不会执行
} else {
    //timer 已经触发或已经被停止
    // 可能的情况:
    // 1. 定时器已经到期,回调已执行或正在执行
    // 2. 之前已经调用过 Stop ()
}

并发安全性

// 场景:超时和手动取消同时发生
ctx, cancel := context.WithTimeout(parent, 100*time.Millisecond)
//goroutine 1: 手动取消
go func() {
    time.Sleep(50 * time.Millisecond)
    cancel()  // 调用 timerCtx.cancel → timer.Stop ()
}()
//goroutine 2: 等待超时
// 定时器在 100ms 后自动调用 c.cancel (true, DeadlineExceeded, nil)
// 结果:两次 cancel 调用都是安全的
// 第一次:设置 c.err,关闭 channel,停止 timer
// 第二次:检测到 c.err != nil,直接返回(幂等性)

# valueCtx:值传递上下文

# 数据结构

type valueCtx struct {
    Context           // 父 context
    key, val any     // 只存储一对 key-value
}

设计限制

  • 一个 valueCtx 实例只存一对 key-value
  • 多个 kv 需要嵌套多层 valueCtx
  • 形成链表结构,查找复杂度 O (N)

为什么不用 map?

方案 优点 缺点 适用场景
map O (1) 查找 需要复制整个 map
内存分配大
存储大量数据
链表 不可变性
结构共享
内存高效
O (N) 查找 少量数据传递

Go 选择链表的原因:

  • Context 通常只存储少量元数据(3-5 个键值对)
  • 不可变性保证并发安全
  • 结构共享避免不必要的复制

# Value 方法:链式查找

func (c *valueCtx) Value(key any) any {
    // 当前节点匹配
    if c.key == key {
        return c.val
    }
    // 递归向父节点查找
    return value(c.Context, key)
}

# 通用查找函数

//value 在 context 链中递归查找指定 key 的值
// 通过类型断言遍历 context 链,避免使用接口方法调用带来的性能开销
func value(c Context, key any) any {
	// 循环遍历 context 链直到找到值或到达链的末尾
	for {
		switch ctx := c.(type) {
		case *valueCtx:
			// 如果是 valueCtx 类型,检查 key 是否匹配
			if key == ctx.key {
				return ctx.val
			}
			// 继续向上游 context 查询
			c = ctx.Context
		case *cancelCtx:
			// 如果查询的是 cancelCtxKey,则返回 cancelCtx 本身
			if key == &cancelCtxKey {
				return c
			}
			// 继续向上游 context 查询
			c = ctx.Context
		case withoutCancelCtx:
			// 如果查询的是 cancelCtxKey 且是 withoutCancelCtx 类型,返回 nil
			// 这实现了通过 WithoutCancel 创建的 context 的 Cause (ctx) == nil 语义
			if key == &cancelCtxKey {
				return nil
			}
			// 继续向上游 context 查询
			c = ctx.c
		case *timerCtx:
			// 如果查询的是 cancelCtxKey,则返回嵌入的 cancelCtx
			if key == &cancelCtxKey {
				return &ctx.cancelCtx
			}
			// 继续向上游 context 查询
			c = ctx.Context
		case backgroundCtx, todoCtx:
			// 到达 context 链的末端,返回 nil
			return nil
		default:
			// 对于其他类型的 context,调用其 Value 方法
			return c.Value(key)
		}
	}
}

查找过程示例

// 构建 context 链
ctx := context.Background()              // emptyCtx
ctx = context.WithValue(ctx, "request-id", "abc123")  // valueCtx 1
ctx = context.WithValue(ctx, "user-id", 42)           // valueCtx 2
ctx, cancel := context.WithTimeout(ctx, 5*time.Second) // timerCtx
ctx = context.WithValue(ctx, "trace-id", "xyz789")    // valueCtx 3
// 查找 "trace-id"
// 路径:valueCtx (trace-id) → 找到,返回 "xyz789"
// 复杂度:O (1)
// 查找 "user-id"
// 路径:valueCtx (trace-id) → timerCtx → valueCtx (user-id) → 找到,返回 42
// 复杂度:O (3)
// 查找 "request-id"
// 路径:valueCtx (trace-id) → timerCtx → valueCtx (user-id) → valueCtx (request-id) → 找到
// 复杂度:O (4)
// 查找 "不存在的 key"
// 路径:完整遍历到 emptyCtx → 返回 nil
// 复杂度:O (5)

链表结构示意图

valueCtx(trace-id="xyz789")
    ↓
timerCtx(deadline=...)
    ↓
valueCtx(user-id=42)
    ↓
valueCtx(request-id="abc123")
    ↓
emptyCtx (Background)

# WithValue:创建值上下文

func WithValue(parent Context, key, val any) Context {
    // 参数校验
    if parent == nil {
        panic("cannot create context from nil parent")
    }
    if key == nil {
        panic("nil key")
    }
    if !reflectlite.TypeOf(key).Comparable() {
        panic("key is not comparable")
    }
    
    // 包装成 valueCtx
    return &valueCtx{parent, key, val}
}

key 的可比较性检查

// ✅ 可比较类型 (OK)
type UserID int
ctx = context.WithValue(ctx, UserID(1), user)
type contextKey string
ctx = context.WithValue(ctx, contextKey("trace-id"), "123")
ctx = context.WithValue(ctx, "request-id", "abc")  //string 可比较
// ❌ 不可比较类型 (Panic)
ctx = context.WithValue(ctx, []string{"key"}, value)      // slice
ctx = context.WithValue(ctx, map[string]int{}, value)     // map
ctx = context.WithValue(ctx, func(){}, value)             // func
// ✅ 结构体(所有字段都可比较时才可比较)
type RequestInfo struct {
    ID   string
    Time time.Time
}
ctx = context.WithValue(ctx, RequestInfo{"req1", time.Now()}, data)

为什么需要可比较性?

// Value 方法使用 == 比较 key
if c.key == key {
    return c.val
}
// 如果 key 不可比较,== 操作会 panic
// 所以在 WithValue 时就提前检查

# 最佳实践:定义专用 key 类型

// ❌ 错误做法:使用字符串
ctx = context.WithValue(ctx, "user-id", 123)
ctx = context.WithValue(ctx, "user-id", "abc")  // 可能被其他包覆盖
// ✅ 正确做法:定义不导出的 key 类型
type contextKey string
const (
    userIDKey contextKey = "user-id"
    traceIDKey contextKey = "trace-id"
)
// 提供类型安全的访问函数
func WithUserID(ctx context.Context, userID int) context.Context {
    return context.WithValue(ctx, userIDKey, userID)
}
func GetUserID(ctx context.Context) (int, bool) {
    userID, ok := ctx.Value(userIDKey).(int)
    return userID, ok
}
// 使用
ctx := WithUserID(context.Background(), 123)
if userID, ok := GetUserID(ctx); ok {
    fmt.Println("User ID:", userID)
}

# 使用限制与反模式

# ❌ 反模式 1:存储大量数据

// 错误:valueCtx 链过长
ctx := context.Background()
for i := 0; i < 100; i++ {
    ctx = context.WithValue(ctx, fmt.Sprintf("key%d", i), i)
}
// 问题:
//- 100 层嵌套
//- 查找最后一个 key 需要遍历 100 次
//- 内存浪费:100 个 valueCtx 结构体

正确做法

type RequestData struct {
    Values map[string]interface{}
}
ctx = context.WithValue(ctx, requestDataKey, &RequestData{
    Values: map[string]interface{}{
        "key0": 0,
        "key1": 1,
        // ...
        "key99": 99,
    },
})
// 优点:只有一层 valueCtx,查找 O (1)

# ❌ 反模式 2:存储可选参数

// 错误:把 context 当作参数包
func processUser(ctx context.Context) {
    userID := ctx.Value("userID").(int)
    userName := ctx.Value("userName").(string)
    userAge := ctx.Value("userAge").(int)
    // ...
}
// 问题:
//- 参数不明确
//- 类型不安全
//- 难以追踪数据流

正确做法

// 使用显式参数
func processUser(ctx context.Context, userID int, userName string, userAge int) {
    // ...
}
// 或使用结构体
type User struct {
    ID   int
    Name string
    Age  int
}
func processUser(ctx context.Context, user User) {
    // ...
}

# ❌ 反模式 3:存储函数或 channel

// 错误:存储复杂类型
ctx = context.WithValue(ctx, "callback", func() { ... })
ctx = context.WithValue(ctx, "channel", make(chan int))
// 问题:
//- 违反 context 的设计初衷
//- 难以测试和维护
//- 生命周期管理复杂

正确做法

// 使用显式参数传递
func process(ctx context.Context, callback func(), ch chan int) {
    // ...
}

# ✅ 正确用法:存储请求范围的元数据

// 适合存储在 context 中的数据
type contextKey string
const (
    requestIDKey  contextKey = "request-id"
    userIDKey     contextKey = "user-id"
    traceIDKey    contextKey = "trace-id"
    authTokenKey  contextKey = "auth-token"
)
// HTTP 中间件
func TraceMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        traceID := generateTraceID()
        ctx := context.WithValue(r.Context(), traceIDKey, traceID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
// 业务逻辑中使用
func handleRequest(ctx context.Context) {
    traceID, _ := ctx.Value(traceIDKey).(string)
    log.Printf("[%s] Processing request", traceID)
}

# 总结与最佳实践

# Context 使用原则

  1. 不要存储 Context

    // ❌ 错误
    type Server struct {
        ctx context.Context  // 不要存储
    }
    // ✅ 正确
    type Server struct {}
    func (s *Server) Serve(ctx context.Context) {
        // 作为参数传递
    }
  2. Context 作为第一个参数

    // ✅ 正确
    func DoSomething(ctx context.Context, arg1 string, arg2 int) error
    // ❌ 错误
    func DoSomething(arg1 string, ctx context.Context, arg2 int) error
  3. 不要传递 nil Context

    // ❌ 错误
    DoSomething(nil, "arg")
    // ✅ 正确
    DoSomething(context.Background(), "arg")
    // 或
    DoSomething(context.TODO(), "arg")
  4. 只传递请求范围的数据

    // ✅ 适合
    - Request ID
    - Trace ID
    - Auth Token
    - User ID
    // ❌ 不适合
    - 数据库连接
    - 配置对象
    - 日志对象
    - 可选参数
  5. 总是 defer cancel ()

    // ✅ 正确
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    // ❌ 错误
    ctx, _ := context.WithTimeout(ctx, 5*time.Second)
    // 忘记 cancel,泄漏 timer

# 性能最佳实践

  1. 避免过深的 context 链

    // ❌ 每次请求都嵌套
    func handleRequest(ctx context.Context) {
        ctx = context.WithValue(ctx, key1, val1)
        ctx = context.WithValue(ctx, key2, val2)
        ctx = context.WithValue(ctx, key3, val3)
        // 链过长,查找慢
    }
    // ✅ 合并数据
    type RequestMeta struct {
        Key1, Key2, Key3 string
    }
    ctx = context.WithValue(ctx, metaKey, &RequestMeta{...})
  2. 复用 Background

    // ✅ 复用单例
    ctx := context.Background()
    // ❌ 不要重复创建
    for i := 0; i < 1000; i++ {
        ctx := context.Background()  // 没必要
    }
  3. 选择合适的 Context 类型

    // 场景 1:不需要取消
    ctx := context.Background()
    // 场景 2:需要手动取消
    ctx, cancel := context.WithCancel(parent)
    // 场景 3:需要超时
    ctx, cancel := context.WithTimeout(parent, 5*time.Second)
    // 场景 4:需要存储数据
    ctx = context.WithValue(parent, key, val)

# 常见陷阱

  1. 忘记检查 Done

    // ❌ 错误
    func worker(ctx context.Context) {
        for {
            doWork()  // 永不停止
        }
    }
    // ✅ 正确
    func worker(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                return  // 响应取消
            default:
                doWork()
            }
        }
    }
  2. 阻塞操作不响应取消

    // ❌ 错误
    func process(ctx context.Context) error {
        data := blockingRead()  // 不响应 ctx.Done ()
        return processData(data)
    }
    // ✅ 正确
    func process(ctx context.Context) error {
        done := make(chan []byte, 1)
        go func() {
            done <- blockingRead()
        }()
        
        select {
        case data := <-done:
            return processData(data)
        case <-ctx.Done():
            return ctx.Err()
        }
    }
  3. context 泄漏

    // ❌ 错误:goroutine 泄漏
    func leak(ctx context.Context) {
        ctx, cancel := context.WithCancel(ctx)
        // 忘记 cancel,goroutine 永不退出
        go func() {
            <-ctx.Done()
            cleanup()
        }()
    }
    // ✅ 正确
    func noLeak(ctx context.Context) {
        ctx, cancel := context.WithCancel(ctx)
        defer cancel()  // 确保清理
        
        go func() {
            <-ctx.Done()
            cleanup()
        }()
    }
  4. 错误的错误处理

    // ❌ 错误:忽略具体错误
    func handle(ctx context.Context) error {
        if ctx.Err() != nil {
            return errors.New("context error")  // 丢失了具体信息
        }
        return nil
    }
    // ✅ 正确:保留错误信息
    func handle(ctx context.Context) error {
        if err := ctx.Err(); err != nil {
            return fmt.Errorf("context error: %w", err)  // 包装原始错误
        }
        return nil
    }
    // ✅ 更好:区分不同错误
    func handle(ctx context.Context) error {
        if err := ctx.Err(); err != nil {
            if errors.Is(err, context.Canceled) {
                // 用户主动取消
                return nil
            }
            if errors.Is(err, context.DeadlineExceeded) {
                // 超时
                return fmt.Errorf("operation timeout: %w", err)
            }
        }
        return nil
    }

# 实战示例

# 示例 1:HTTP 服务器优雅关闭

func runServer() error {
    server := &http.Server{Addr: ":8080"}
    
    // 创建根 context
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    
    // 监听系统信号
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
    
    // 启动服务器
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Printf("Server error: %v", err)
            cancel()
        }
    }()
    
    // 等待信号或 context 取消
    select {
    case sig := <-sigChan:
        log.Printf("Received signal: %v", sig)
    case <-ctx.Done():
        log.Printf("Context canceled")
    }
    
    // 优雅关闭
    shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer shutdownCancel()
    
    if err := server.Shutdown(shutdownCtx); err != nil {
        return fmt.Errorf("server shutdown failed: %w", err)
    }
    
    log.Println("Server stopped gracefully")
    return nil
}

# 示例 2:并发请求处理

func fetchUserData(ctx context.Context, userID int) (*UserData, error) {
    // 设置超时
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    // 并发请求多个服务
    type result struct {
        profile *Profile
        orders  []Order
        err     error
    }
    
    profileCh := make(chan result, 1)
    ordersCh := make(chan result, 1)
    
    // 获取用户资料
    go func() {
        profile, err := fetchProfile(ctx, userID)
        profileCh <- result{profile: profile, err: err}
    }()
    
    // 获取订单列表
    go func() {
        orders, err := fetchOrders(ctx, userID)
        ordersCh <- result{orders: orders, err: err}
    }()
    
    // 收集结果
    var profile *Profile
    var orders []Order
    var errs []error
    
    for i := 0; i < 2; i++ {
        select {
        case res := <-profileCh:
            if res.err != nil {
                errs = append(errs, fmt.Errorf("fetch profile: %w", res.err))
            } else {
                profile = res.profile
            }
        case res := <-ordersCh:
            if res.err != nil {
                errs = append(errs, fmt.Errorf("fetch orders: %w", res.err))
            } else {
                orders = res.orders
            }
        case <-ctx.Done():
            return nil, fmt.Errorf("fetch timeout: %w", ctx.Err())
        }
    }
    
    if len(errs) > 0 {
        return nil, fmt.Errorf("fetch errors: %v", errs)
    }
    
    return &UserData{
        Profile: profile,
        Orders:  orders,
    }, nil
}

# 示例 3:数据库事务管理

func transferMoney(ctx context.Context, db *sql.DB, from, to int, amount float64) error {
    // 设置事务超时
    ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
    defer cancel()
    
    // 开启事务
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fmt.Errorf("begin transaction: %w", err)
    }
    defer tx.Rollback()  // 如果没有 commit,自动回滚
    
    // 扣款
    _, err = tx.ExecContext(ctx,
        "UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
        amount, from, amount)
    if err != nil {
        return fmt.Errorf("debit failed: %w", err)
    }
    
    // 加款
    _, err = tx.ExecContext(ctx,
        "UPDATE accounts SET balance = balance + ? WHERE id = ?",
        amount, to)
    if err != nil {
        return fmt.Errorf("credit failed: %w", err)
    }
    
    // 提交事务
    if err := tx.Commit(); err != nil {
        return fmt.Errorf("commit transaction: %w", err)
    }
    
    return nil
}

# 示例 4:Worker Pool 模式

type Job struct {
    ID   int
    Data string
}
func workerPool(ctx context.Context, numWorkers int, jobs <-chan Job) {
    var wg sync.WaitGroup
    
    // 启动 worker
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func(workerID int) {
            defer wg.Done()
            worker(ctx, workerID, jobs)
        }(i)
    }
    
    // 等待所有 worker 完成
    wg.Wait()
    log.Println("All workers stopped")
}
func worker(ctx context.Context, id int, jobs <-chan Job) {
    log.Printf("Worker %d started", id)
    defer log.Printf("Worker %d stopped", id)
    
    for {
        select {
        case <-ctx.Done():
            log.Printf("Worker %d: context canceled", id)
            return
            
        case job, ok := <-jobs:
            if !ok {
                log.Printf("Worker %d: channel closed", id)
                return
            }
            
            // 处理任务
            if err := processJob(ctx, job); err != nil {
                log.Printf("Worker %d: job %d failed: %v", id, job.ID, err)
            } else {
                log.Printf("Worker %d: job %d completed", id, job.ID)
            }
        }
    }
}
func processJob(ctx context.Context, job Job) error {
    // 设置任务超时
    ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
    defer cancel()
    
    // 模拟处理
    select {
    case <-time.After(time.Second):
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}
// 使用示例
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    
    jobs := make(chan Job, 100)
    
    // 启动 worker pool
    go workerPool(ctx, 5, jobs)
    
    // 提交任务
    for i := 0; i < 20; i++ {
        jobs <- Job{ID: i, Data: fmt.Sprintf("data-%d", i)}
    }
    close(jobs)
    
    // 等待完成或超时
    time.Sleep(10 * time.Second)
    cancel()  // 取消所有 worker
}

# 示例 5:请求链路追踪

type contextKey string
const (
    traceIDKey contextKey = "trace-id"
    spanIDKey  contextKey = "span-id"
)
// 中间件:注入 trace ID
func TraceMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 从 header 获取或生成 trace ID
        traceID := r.Header.Get("X-Trace-ID")
        if traceID == "" {
            traceID = generateTraceID()
        }
        
        // 注入 context
        ctx := context.WithValue(r.Context(), traceIDKey, traceID)
        ctx = context.WithValue(ctx, spanIDKey, generateSpanID())
        
        // 设置响应 header
        w.Header().Set("X-Trace-ID", traceID)
        
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
// 业务逻辑
func handleRequest(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    
    // 获取 trace 信息
    traceID, _ := ctx.Value(traceIDKey).(string)
    spanID, _ := ctx.Value(spanIDKey).(string)
    
    log.Printf("[%s:%s] Handling request", traceID, spanID)
    
    // 调用下游服务
    data, err := callDownstream(ctx, "https://api.example.com/data")
    if err != nil {
        log.Printf("[%s:%s] Downstream error: %v", traceID, spanID, err)
        http.Error(w, "Internal error", 500)
        return
    }
    
    json.NewEncoder(w).Encode(data)
}
// 下游调用:传递 trace 信息
func callDownstream(ctx context.Context, url string) (interface{}, error) {
    traceID, _ := ctx.Value(traceIDKey).(string)
    newSpanID := generateSpanID()
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    
    // 传递 trace 信息
    req.Header.Set("X-Trace-ID", traceID)
    req.Header.Set("X-Span-ID", newSpanID)
    req.Header.Set("X-Parent-Span-ID", ctx.Value(spanIDKey).(string))
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    var data interface{}
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
        return nil, err
    }
    
    return data, nil
}
func generateTraceID() string {
    return fmt.Sprintf("%016x", time.Now().UnixNano())
}
func generateSpanID() string {
    return fmt.Sprintf("%08x", rand.Uint32())
}

# 参考资料

  • Go Context 官方文档
  • Go 1.21 Release Notes
  • Context 源码
  • Effective Go - Context

最后的建议

  1. 理解设计哲学:Context 是用于传递取消信号和请求范围数据的,不是万能的参数传递工具
  2. 遵循最佳实践:作为第一个参数,不存储,总是 defer cancel
  3. 注意性能影响:避免过深的嵌套,合理使用 WithValue
  4. 响应取消信号:在长时间运行的操作中定期检查 ctx.Done ()
  5. 保持简单:不要过度使用 Context,显式参数往往更清晰

Context 是 Go 并发编程的基石,正确使用它能让你的程序更加健壮和高效!

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

ZJM 微信支付

微信支付

ZJM 支付宝

支付宝