Go Context

Go Context

一、介绍

Context (上下文) 是 Go 语言标准库 context 包提供的核心工具。

用于在goroutine之间传递取消信号、超时控制,元数据等。

二、接口定义

go 复制代码
type Context interface {
    // 取消信号:返回一个通道,当Context被取消/超时时,通道会关闭
    Done() <-chan struct{}
    // 取消原因:返回Context结束的原因(nil表示未结束)
    Err() error
    // 超时时间:返回Context的截止时间(ok=false表示无截止时间)
    Deadline() (deadline time.Time, ok bool)
    // 元数据:根据key获取绑定的value(协程安全)
    Value(key any) any
}

2.1 Done()

如果 Done 没有被 close,Err 方法返回 nil;如果 Done 被 close,Err 方法会返回 Done 被 close 的原因

三、Context 的创建方式

context 包提供了 4 种常用的 Context 创建函数,遵循父子继承原则(子 Context 基于父 Context 派生,父取消则子全部取消)

3.1 根 Context:background & todo

go 复制代码
func Background() Context {
	return backgroundCtx{}
}
func TODO() Context {
	return todoCtx{}
}

Background()和 TODO()是两个用于创建根上下文(root context)的函数

3.1.1 Background()函数
  • 作用 :返回一个非nil 的空的 Context。它没有任何值,不会被 cancel,不会超时,也没有截止时间。
  • 典型用途
    *
    1. 作为 main函数的初始上下文。
      1. 作为初始化或测试的顶级上下文。
      1. 作为传入请求的根上下文。
  • 使用场景
    • 当没有其他上下文可用时,应使用 Background()作为起点。
    • 通常用于主函数、初始化或测试中。
go 复制代码
package main

import (
	"context"
	"fmt"
)

func main() {
	ctx := context.Background() // 根Context
	// ctx := context.TODO() // 临时占位,和Background等价
	fmt.Println("Done channel:", ctx.Done()) // nil(根Context不会取消)
	fmt.Println("Err:", ctx.Err())           // nil
	dateTime, ok := ctx.Deadline()
	fmt.Println("Is Deadline:", ok)         //  false
	fmt.Println("Deadline:", dateTime)      // 0001-01-01 00:00:00 +0000 UTC
	fmt.Println("Value:", ctx.Value("key")) // nil
}

PS E:\goCode\context> go run .\main.go
Done channel: <nil>
Err: <nil>
Is Deadline: false
Deadline: 0001-01-01 00:00:00 +0000 UTC
Value: <nil>
PS E:\goCode\co
3.1.2 TODO()函数
  • 作用:同样返回一个非nil 的、空的 Context。其语义与 Background()相同。
  • 使用场景:
    *
    1. 当不确定该使用哪个 Context时。
      1. 当周围的函数尚未扩展以接受 Context参数时(作为占位符使用)。
  • 注意:应避免在生产代码中保留 TODO(),它仅作为临时使用,提示开发者将来需要替换为合适的上下文
go 复制代码
package main

import (
   "context"
   "fmt"
)

func main() {
   ctx := context.TODO()                    // 根Context
   fmt.Println("Done channel:", ctx.Done()) // nil(根Context不会取消)
   fmt.Println("Err:", ctx.Err())           // nil
   dateTime, ok := ctx.Deadline()
   fmt.Println("Is Deadline:", ok)         //  false
   fmt.Println("Deadline:", dateTime)      // 0001-01-01 00:00:00 +0000 UTC
   fmt.Println("Value:", ctx.Value("key")) // nil
}
PS E:\goCode\context> go run .\main.go
Done channel: <nil>
Err: <nil>
Is Deadline: false
Deadline: 0001-01-01 00:00:00 +0000 UTC
Value: <nil>
3.1.3 区别
  • Background()是默认的起始上下文,应在已知用途时使用。
  • TODO()临时占位符,表明上下文尚未确定,需要在未来替换

3.2 可取消 Context:WithCancel

go 复制代码
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}
3.2.1 核心功能
  • 创建派生上下文:基于父上下文创建新的上下文
  • 提供取消机制:返回一个可取消的上下文和对应的取消函数
  • 传播取消信号:新上下文会监听父上下文的取消信号
3.2.2 关键特性
  1. 取消触发条件

    返回的上下文会在以下任一情况发生时关闭其 Done通道

    • 调用返回的 cancel函数
    • 父上下文的 Done通道被关闭
  2. 取消传播机制

    • cancel 是向下传递
    • 子上下文的取消不会影响父上下文
    • 父上下文的取消自动传播到所有子上下文
  3. 调用 cancel 释放相关资源

    不是只有你想中途放弃,才去调用 cancel,只要你的任务正常完成了,就需要调用 cancel,否则,资源不能得到释放。

go 复制代码
// 最佳实践是尽快调用 cancel
defer cancel()  // 通常使用 defer 确保调用
3.2.3 用途

需要主动取消长时间的任务

go 复制代码
package main

import (
	"context"
	"fmt"
	"time"
)

func worker(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("worker done")
			return
		case <-time.After(time.Second):
			fmt.Println("worker working")
		}
	}
}
func main() {
	// 创建可取消的上下文
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // 确保最终调用取消,避免Goroutine泄漏

	go worker(ctx)

	time.Sleep(5 * time.Second)
	cancel()

	time.Sleep(time.Second)
	fmt.Println("main done")
}
PS E:\goCode\context> go run .\main.go
worker working
worker working
worker working
worker working
worker done
main done

级联

ctx1被取消 → ctx2也被取消

go 复制代码
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// 创建根上下文
	rootCtx := context.Background()

	// 创建子上下文1
	ctx1, cancel1 := context.WithCancel(rootCtx)
	defer cancel1()

	// 创建子上下文2(从ctx1派生)
	ctx2, cancel2 := context.WithCancel(ctx1)
	defer cancel2()

	go func() {
		<-ctx2.Done()
		fmt.Println("ctx2 被取消:", ctx2.Err())
	}()

	go func() {
		<-ctx1.Done()
		fmt.Println("ctx1 被取消:", ctx1.Err())
	}()

	cancel1()
	time.Sleep(100 * time.Millisecond)
}
PS E:\goCode\context> go run .\main.go
ctx2 被取消: context canceled
ctx1 被取消: context canceled

3.3 超时 Context:WithTimeout

go 复制代码
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

指定超时时间,超时后自动取消

go 复制代码
package main

import (
	"context"
	"fmt"
	"time"
)

func request(ctx context.Context, url string) {
	select {
	case <-ctx.Done():
		fmt.Printf("请求 %s 失败:%v\n", url, ctx.Err())
		return
	case <-time.After(1 * time.Second): // 模拟请求耗时1秒
		fmt.Printf("请求 %s 成功\n", url)
	}
}

func main() {
	// 创建超时Context(1.5秒超时)
	ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond)
	defer cancel()

	// 启动请求协程
	go request(ctx, "https://example.com")

	// 等待结果
	time.Sleep(2 * time.Second)
	fmt.Println("主协程:结束")
}

3.4 截止时间 Context:WithDeadline

指定具体截止时间(比如 time.Date(2026, 3, 22, 10, 0, 0, 0, time.Local)),到达时间后自动取消

go 复制代码
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// 设定截止时间为1秒后
	deadline := time.Now().Add(1 * time.Second)
	ctx, cancel := context.WithDeadline(context.Background(), deadline)
	defer cancel()

	select {
	case <-time.After(2 * time.Second):
		fmt.Println("操作完成")
	case <-ctx.Done():
		fmt.Println("操作超时:", ctx.Err()) // context deadline exceeded
	}
}

3.5 带元数据 Context:WithValue

用于传递少量协程安全的元数据(比如请求 ID、用户 Token),key 建议定义为自定义类型(避免全局 key 冲突)

go 复制代码
package main

import (
	"context"
	"fmt"
)

// 定义自定义key类型(避免冲突)
type ctxKey string

const (
	RequestIDKey ctxKey = "request_id"
	UserIDKey    ctxKey = "user_id"
)

func handler(ctx context.Context) {
	// 获取元数据
	reqID := ctx.Value(RequestIDKey).(string)
	userID := ctx.Value(UserIDKey).(int)
	fmt.Printf("处理请求:reqID=%s, userID=%d\n", reqID, userID)
}

func main() {
	// 基于根Context添加元数据
	ctx := context.WithValue(context.Background(), RequestIDKey, "req-123456")
	ctx = context.WithValue(ctx, UserIDKey, 1001) // 链式添加

	handler(ctx)
}

四、使用注意

  • 不要存储 Context:Context 应通过参数传递,而非存储在结构体 / 全局变量中。
  • 根 Context 选择 :生产环境用 context.Background(),临时占位用 context.TODO()
  • 取消必调用WithCancel/WithTimeout/WithDeadline 返回的 cancel 函数必须调用(即使提前完成),避免 Goroutine 泄漏。
  • 参数传递 :Context 作为函数第一个参数,命名为 ctx(比如 func doSomething(ctx context.Context, arg int))

五、常见使用场景

  • HTTP 服务:控制请求超时(比如 Gin/Net/http 中传递 Context);
  • 数据库操作:取消慢查询(如 sql.DB.QueryContext(ctx, sql));
  • RPC 调用:跨服务传递取消信号(如 gRPC 内置 Context 支持);
  • 批量任务取消未完成的子任务(比如批量处理文件时,某个任务失败则全部取消)。
相关推荐
AMoon丶2 小时前
Golang--垃圾回收
java·linux·开发语言·jvm·后端·算法·golang
Dylan~~~2 小时前
Go语言Web框架选型指南:从入门到精通
开发语言·前端·golang
hongtianzai2 小时前
Laravel7.x十大核心特性解析
java·c语言·开发语言·golang·php
Java面试题总结3 小时前
go从零单排之方法
开发语言·后端·golang
lars_lhuan3 小时前
Go atomic
开发语言·后端·golang
hongtianzai4 小时前
Laravel8.x核心特性全解析
java·c语言·开发语言·golang·php
参.商.13 小时前
【Day41】143. 重排链表
leetcode·golang
捧 花16 小时前
最小生成树算法(Go)
golang·最小生成树·kruskal·prim
添尹17 小时前
Go语言基础之数组
后端·golang