2025/08/202025/08/20技术约 2 分钟853 字

go context学习之旅

context 数据结构

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key interface{}) interface{}
}

这是 go 官方的 context 包,其中的Context数据结构实现了以下四个方法

  • Deadline:获取 context 的截止时间,ok 表示是否设置了超时时间。
  • Done:当 context 被取消或达到截止时间时,返回的只读 channel 会被关闭,用来传递取消信号。
  • Err:在 Done 关闭后返回错误信息(context.Canceled 或 context.DeadlineExceeded),在此之前返回 nil。
  • Value:获取 context 中存储的值(通常用来传递请求范围内的元数据,如 traceID、用户信息)。

创建 context

context.Background() — 根 context context.TODO() — 占位,类型和 Background 一致但是未确定是使用根 context 还是传递的 context

这两个都是根 context,然后均为无超时,无取消,无值的“三无”根 context

来看一段代码

func TestDeadline() {
	ctx := context.Background()

	deadTime, ok := ctx.Deadline()
	fmt.Println("超时时间和是否设置超时", deadTime, ok)


	// 死锁, 因为context.Background()是无超时
	// <-ctx.Done()

	fmt.Printf("ctx.Err(): %v\n", ctx.Err())

}

输出结果

超时时间和是否设置超时 0001-01-01 00:00:00 +0000 UTC false
ctx.Err(): <nil>

注释的部分是因为根 context 无超时时间,所以 channel 永久不会,发送超时信号【channel 在退出的时候,为空继续拿值会拿其零值】,一直阻塞造成死锁

context 的派生

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
  • WithCancel,可用于创建子 context,附带撤销机制
  • WithDeadline,可用于创建子 context,附带撤销机制,同时附带超时控制
  • WithTimeout,类似 WithDeadline,但是传递的 duration,方便些
  • WithValue,父 context 的派生,设置值

context 通过根 context,通过四个 with 系列方法可以派生出四种类型的 context,每种 context 又可以通过同样的方式调用 with 系列方法继续向下派生新的 context,整个结构像一棵树。

来看一段代码

func func1(ctx context.Context, name string) {
	select {
	case <-ctx.Done():
		fmt.Printf("%s, 超时了, 准备退出...\n", name)
		fmt.Println("ctx.Err", ctx.Err().Error())
	case <-time.After(3 * time.Second):
		fmt.Printf("%s, 数据库查询完成\n", name)
	}
}

func func2(ctx context.Context) {
	fmt.Printf("ctx.Value(\"name\").(string): %v\n", ctx.Value("name").(string))
}

func main() {
	/* ---------- context派生 ---------- */
	ctx1, cancel := context.WithCancel(parent)
	go func1(ctx1, "cancel")
	time.Sleep(2 * time.Second)
	cancel() // 子goroutine 2s之后超时退出

	ctx2, cancel2 := context.WithDeadline(parent, time.Now().Add(1*time.Second))
	go func1(ctx2, "deadline") // 子goroutine 1s之后超时退出
	defer cancel2()

	ctx3, cancel3 := context.WithTimeout(parent, 500*time.Millisecond)
	go func1(ctx3, "timeout") // 子goroutine 0.5s之后超时退出
	defer cancel3()

	ctx4 := context.WithValue(parent, "name", "codepzj")
	go func2(ctx4)

	time.Sleep(5 * time.Second)
}

输出结果:

ctx.Value("name").(string): codepzj
cancel, 超时了, 准备退出...
ctx.Err context canceled
timeout, 超时了, 准备退出...
ctx.Err context deadline exceeded
deadline, 超时了, 准备退出...
ctx.Err context deadline exceeded

context 用途

context 主要有两个用途,也是在项目中经常使用的:

  1. 用于并发控制,控制协程的优雅退出
  2. 上下文的信息传递,总的来说,context 就是用来在父子 goroutine 间进行值传递以及发送 cancel 信号的一种机制。

评论区