Golang之上下文Context

Golang之上下文Context

1.Context

在Go语言并发编程中,用一个goroutine来处理一个任务 ,而它又会创建多个goroutine来负责不同子任务的创建非常常见,这些场景中往往会需要在API边界之间以及过程之间,传递截止时间,取消信号,或其他与请求相关的数据,这时候就可以使用Context,context包在Go1.7的时候被加入到官方库中。
主要内容可以概括为一个接口,四种具体实现,还有六个函数。

在这里插入图片描述

2.emptyCtx接口

Context接口提供了四个方法,emptyCtx本质上是个整型,*emptyCtx对Context接口的实现只是简单的返回nil,false等等,时机实际上什么也没做。

在这里插入图片描述

3.Backgroud函数

Backgroud和TODO这两个函数内部都会创建emptyCtx。

在这里插入图片描述

其中Background主要用于在初始化时获取一个Context,这里变量ctx是非空接口类型。还记得非空接口的结构吗,itab这里的接口类型为Context,动态类型为*emptyCtx,data指向一个emptyCtx,本质上就是一个int,这就是Background函数返回的变量结构。

在这里插入图片描述

4.TODO函数

而TODO函数,官方文档建议在本来应该使用外层传递的ctx,而外层却没有传递的地方使用,就像函数名称表达的含义一样,留下一个TODO。

在这里插入图片描述

5.cancelCtx接口

再来看cancelCtx类型,这是一种可取消的Context。
done用于获取该Context的取消通知;
children用于存储以当前节点为根节点的所有可取消的Context。以便在根节点取消时,可以把它们一并取消;
err用于存储取消时指定的错误信息;
而mu就是用来保护这几个字段的锁,以保障cancelCtx是线程安全的

在这里插入图片描述

6.WitchCancel函数

而WitchCancel函数可以把一个Context包装为cancelCtx,并提供一个取消函数,调用它可以取消(cancel)对应的Context。

在这里插入图片描述

例如这里,我们基于ctx创建一个cancelCtx,ctx1也是非空接口,itab这里的接口类型是Context,动态类型为*cancelCtx,data指向一个cancelCtx结构体,第一个字段存储的是它的父级Context

在这里插入图片描述

7.timeCtx接口

再来看timerCtx,它在cancelCtx的基础上,又封装了一个定时器和一个截止时间,这样既可以根据需要主动取消,也可以在到达deadline时,通过timer来触发取消动作。要注意这个timer也会由cancelCtx.mu来保护,确保取消操作也是线程安全的。

在这里插入图片描述

8.WithDeadline函数、WithTimeout函数

通过WithDeadline和WithTimeout函数,都可以创建timerCtx,区别是WithDeadline函数需要制定一个时间点,而WithTimeout函数接受一个时间段。

在这里插入图片描述

接下来我们使用WithDeadline基于ctx1构造一个timerCtx, ctx2同样是Context接口类型,动态类型为*timerCtx,data指向一个timerCtx结构体。第一个字段是一个cancelCtx,这个cancelCtx是基于ctx1创建的。后面是一个定时器和一个截止时间。这个定时器会在deadline到达时调用cancelCtx的取消函数。

在这里插入图片描述

现在可以看到ctx2是基于ctx1创建的,ctx1是又是基于ctx创建的,基于每个Context可以创建多个Context,这样就形成了一颗Context数,每个节点都可以由零个或多个子节点,可取消的Context都会被注册到离他最近的,可取消的祖先节点中。对ctx2来说离他最近的,可取消的祖先节点是ctx1,所以在ctx1这里的children map中,会增加ctx2这组键值对。

在这里插入图片描述
在这里插入图片描述

如果ctx2先取消,就只会影响到以它为根节点的Context,

在这里插入图片描述

而如果是ctx1先取消,就可以根据children map中的记录,把ctx1子节点中所以可取消的Context全部cancel掉。

在这里插入图片描述

9.valueCtx接口、WithValue函数

最后来看valueCtx类型,它用来支持键值对打包,WithValue函数,可以给Context附加一个键值对信息,这样就可以通过Context传递数据了

在这里插入图片描述

现在我们给ctx附加一个键值对,<keyA,valA>,变量ctxA也是Context接口类型 ,动态类型为*valueCtx,data指向一个valueCtx结构体。第一个字段是它的父级Context,key和val字段都是空接口类型。key的动态类型为string,动态值是string类型的变量“keyA”,val的动态类型同样是string, 动态值为“valA”。

在这里插入图片描述

下面我们再基于ctxA,附加一个key相等但是val不相等的键值对。ctxC的动态值指向这样一个valueCtx,父级Context自然是ctxA,key的动态类型为string,动态值是string类型的变量"keyA",与ctxA中的相同,但是val的值与ctxA中的不相等,通过ctxC获取keyA和keyC对应的值时,会发现keyC覆盖了keyA对应的val

在这里插入图片描述

要找到原因,就要先看看Value的方法是怎样工作的,首先它会比较当前Context中的key是否等于要查找的key,因为keyA等于keyC,所以keyA的查找会直接锁定到ctxC这里的val,因而出现了子节点覆盖父节点数据的情况

在这里插入图片描述

为了规避这种情况,最好不要直接使用string,int这些基础类型作为key,而是用自定义类型包装一下。就像这样,把keyA定义为keytypea类型,keyC定义为keytypeC类型,这样再次通过ctxC获取keyA时,因为key的类型不相同,第一步key相等性的比较不通过,就会委托父节点继续查找,进而找到正确的val。

在这里插入图片描述

所以说valueCtx之间通过Context字段,形成了一个链表结构,使用Context传递数据时还要注意,Context本身是本着不可改变(immutable)的模式设计的,所以不要试图修改ctx里面保存的值

在这里插入图片描述

在http,sql相关的库中,都提供了对Context的支持,方便我们在处理请求时,实现超时自动取消,或传递请求相关的控制数据等等

在这里插入图片描述

Context基础内容:一个接口,四种实现,六个函数。帮你理解Context的工作原理。

在这里插入图片描述

### Golang 中 `context` 的底层实现原理 #### 1. Context 接口定义 在 Go 语言中,`context` 是通过一个接口来定义的。这个接口包含了四个方法:`Deadline()`、`Done()`、`Err()` 和 `Value(key interface{}) interface{}`[^3]。这些方法提供了控制 Goroutine 生命周期的能力以及跨多个 Goroutines 共享数据的功能。 ```go type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} } ``` #### 2. 空上下文 (`emptyCtx`) 为了提供不可取消的默认上下文Go 提供了一个名为 `emptyCtx` 的结构体,它实现了 `Context` 接口并返回固定的值。例如: - 调用 `Background()` 或 `TODO()` 方法会返回预初始化的全局变量 `background` 和 `todo`。 - 这些变量在整个程序生命周期内不会被修改或关闭[^4]。 以下是相关代码片段: ```go var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo } ``` #### 3. 取消功能的实现 当需要支持取消操作时,可以使用 `cancelCtx` 类型。该类型继承自 `Context` 并额外维护了一个 `mu` 锁、一个布尔标志位 `done` 以及一个等待组 `waitGroup` 来管理子上下文的状态变化。 创建可取消的上下文通常依赖于以下两个函数之一: - `WithCancel(parent Context)` 创建一个新的上下文对象及其关联的取消函数。 - 当调用此取消函数时,所有监听到 `ctx.Done()` 频道信号的协程都将退出运行。 下面是一个简单的例子展示如何利用 WithCancel 构造器生成带超时机制的新实例: ```go parent := context.Background() ctx, cancel := context.WithCancel(parent) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("Operation took too long...") cancel() case <-ctx.Done(): fmt.Println("Received cancellation request:", ctx.Err()) } ``` #### 4. 数据传递机制 除了作为协作工具外,`context` 还允许开发者附加键值对形式的小量元信息给特定请求链路上的所有参与者共享访问权限。这主要是借助 `WithValue` 函数完成的。 注意,在实际项目开发过程中应谨慎使用此类特性以免造成潜在的安全隐患或者性能瓶颈问题。 示例演示如下所示: ```go package main import ( "context" "fmt" ) func main() { ctx := context.Background() ctx = context.WithValue(ctx, "userID", 12345) go handleRequest(ctx) time.Sleep(time.Millisecond * 500) // Simulate waiting for goroutine to finish. } func handleRequest(ctx context.Context) { userID := ctx.Value("userID").(int) fmt.Printf("Handling request from user %d\n", userID) } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值