Go语言Context (ctx)的基础概念与用法

本文详细介绍了Golang中context的作用,包括其设计原理、常见用法(如控制goroutine和传递元数据),以及超时控制和使用注意事项。特别强调了context在goroutine管理和数据传递中的优雅性及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

ctx字面意思上下文,是golang中特有的一种语法,几乎每一个程序中都会通篇传递着一个ctx。而一些框架又对其进行二次封装,诸如Gin框架中的c *gin.Context。因此此次进行ctx的学习并记录。

设计原理

这是ctx的接口部分,其提供了一个接口及许多函数、结构体(如图)。

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

在这里插入图片描述

其通过context.Background、context.TODO、context.WithDeadline 和 context.WithValue来返回实现这个接口的结构体。

goroutine树和ctx是上下传递的,会从顶层一步步传往底层。详见

在这里插入图片描述

常见用法

控制goroutine

多个goroutine运行,如何控制其结束?
最Low的办法自然是造一个bool全局变量,多个goroutine中检测其bool值,若改变,则停止。
但这样显然是不合理的,于是便可以通过select+chan来进行控制,即在goroutine中放一个select,其中通过chan到某个值,就停止。

func main() {
	stop := make(chan bool)

	go func() {
		for {
			select {
			case <-stop:    // 收到了停滞信号
				fmt.Println("监控退出,停止了...")
				return
			default:
				fmt.Println("goroutine监控中...")
				time.Sleep(2 * time.Second)
			}
		}
	}()

	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知监控停止")
	stop<- true

	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

但这样也有很大的局限性,比如我的goroutine中又新建了goroutine,这种无法在程序开始前写到的地方,该如何接收?
因此便可以用到ctx来进行控制。

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				fmt.Println("监控退出,停止了...")
				return
			default:
				fmt.Println("goroutine监控中...")
				time.Sleep(2 * time.Second)
			}
		}
	}(ctx)

	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel()
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

两个方法看似一样,只是将手动向<-stop收发,换成了使用cancel()<-ctx.Done()。根据源码可以看出,其底层原理也是通过chan在进行通信,但是由于ctxgoroutine树,所以可以解决goroutine中新建goroutine的问题,从而更优雅的关闭goroutine。

传递元数据

通过ctx.Value("key")来读取,通过ctx = context.WithValue(ctx, "key", value)来设置。

但仅建议传递内置类型的数据。不建议传递重要数据,比如传递 签名、trace_id这类值。

超时控制

即通过context.WithDeadline(ctx, time.Now().Add(10*time.Second))设置这个ctx的关闭时间。

通过设置deadline,可以防止一个请求超时导致服务器问题。

需要注意的是不同ctx中deadline不一致导致问题。
(一次我使用context.Context*gin.Context时遇到了莫名其妙的超时,就是因为这个不一致)

使用注意事项

1、不要把 Context 放在结构体中,要以参数的方式传递。
2、以 Context 作为参数的函数方法,应该把 Context 作为第一个参数,放在第一位。
3、给一个函数方法传递 Context 的时候,不要传递 nil,如果不知道传递什么,就使用 context.TODO。
4、Context 的 Value 相关方法应该传递必须的数据,不要什么数据都使用这个传递。
5、Context 是线程安全的,可以放心的在多个 goroutine 中传递。

参考文献

6.1 上下文 Context #

深度解密Go语言之context

一文吃透 Go 语言解密之上下文 Context

【Go语言】小白也能看懂的context包详解:从入门到精通

Golang 中 context 的使用方式

### Go语言 `context` 包使用教程 #### 创建根上下文 在Go语言中,程序通常会从两个预定义函数获取顶级上下文:`background()` 或者 `todo()`。这两个函数返回的上下文对象一般作为整个调用链最顶层的起点[^3]。 ```go package main import ( "context" ) func main() { ctx := context.Background() } ``` #### 添加截止时间到上下文中 通过 `WithDeadline` 方法可以为某个操作设置一个结束的时间点,在这个时间之后,所有的子goroutine都会收到通知并停止工作[^1]。 ```go package main import ( "context" "fmt" "time" ) func main() { d := time.Now().Add(50 * time.Millisecond) ctx, cancel := context.WithDeadline(context.Background(), d) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) } } ``` #### 取消子协程执行 利用 `WithCancel` 函数可以从父goroutine主动触发取消事件给所有依赖于该上下文的子goroutine,从而实现优雅关闭资源的目的。 ```go package main import ( "context" "fmt" "time" ) func worker(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("worker exiting:", ctx.Err()) return default: fmt.Println("working...") time.Sleep(time.Second) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) go worker(ctx) time.Sleep(3 * time.Second) cancel() time.Sleep(time.Second) } ``` #### 值传递 有时候需要向下游服务传递一些不敏感的数据,这时就可以借助 `WithValue` 来附加键值对形式的信息至上下文中[^5]。 ```go package main import ( "context" "fmt" ) type userKey string func main() { type key int userID := key(42) ctx := context.WithValue(context.Background(), userID, "jane") v := ctx.Value(userID).(string) fmt.Println(v == "jane") // true } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值