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

前言

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
}

Go语言Context (ctx)的基础概念与用法_第1张图片

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

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

Go语言Context (ctx)的基础概念与用法_第2张图片

常见用法

控制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 的使用方式

你可能感兴趣的:(Golang,golang,开发语言)