golang中context.WithValue的使用规范问题小结

 更新时间:2025年02月17日 10:34:01   作者:大口吃饭大口吐  
本文主要介绍了golang中context.WithValue的使用规范问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

我们首先来看一个报错

should not use built-in type string as key for value; define your own type to avoid collisions (SA1029)go-staticcheck

代码长这样

image.png

上面这段程序的波浪线报错不是warnning级别的,可以说是编辑器的提示,虽说不影响程序的打包运行,但作为强迫症玩家,属实难以接受

这里是golang针对context.Context 类型的使用定义的规范,下面是一些官方话术的解释

  • 使用context.Context时,不应该使用内置类型作为 KV 中 key 的类型,而应该使用自定义的类型来避免冲突。
  • 当使用 context.Context 类型保存 KV 对时, key 不能使用原生类型,而应该使用派生类型

我们看一些案例

我们在项目中,通常利用 context.Context 作为一个生命周期的上下文传递,贯穿全局,经常来存一些自定义的键值对,保存新的 context 对象

ctx = context.WithValue(ctx, someKey, someValue)

WithValue方法标准库的定义为
key 和 val 都是any类型

func WithValue(parent Context, key, val any) Context {
	……

}

该方法的注释有这么一句context keys often have concrete type struct{}就是建议 key 的类型通常为具体的结构体类型

我们的实际使用中,大多会这么样写

ctx = context.WithValue(ctx, "openid", userOpenID)

潜在问题

现在的项目往往涉及多个包的紧密耦合和团队成员间的协作。在一个 ctx 对象的生命周期中,它需要穿越多个逻辑层或包,每个模块都有可能利用 ctx 来存储相关信息。

以用户模块为例,它可能会使用 ctx 来缓存用户的 openid 字段。这种做法本身是合理的。随后,这个 ctx(以及相应的代码逻辑)继续流转,大家默认使用这个 “openid” 键来存储数据。

然而,当一个紧急需求出现,比如需要快速开发一个群聊功能,并且尽可能地复用现有代码以减少开发工作量时,问题就出现了。可能群聊模块在利用用户模块的代码时,无意中也使用了 “openid” 这个键,这次是用来存储群主的 openid。结果,当代码运行时,支援的开发人员发现了一个奇怪的现象:群主的 openid 似乎在不断地变化,仿佛群主的身份在不断轮换。

处理办法

我们以一种常见的思维方式来处理,大家通常会说对ctx里的 key 里的内容统一规范管理,大家操作ctx时都遵循一套规则, 这的确是一个很不错的办法

但是我现在对统一规范管理,这6个字特别厌恶,动不动就统一管理的,随着岁月的流失谁还会想着去看文档,干点儿啥都去先看文档约束,麻烦

这种局部的工作细节,分而治之,尽可能避免集中式的管理显然更加适用,又不是什么大的模块

我们先来一个小小的对比优化代码

type chatGroup string

func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, "openid", "不是群主")
    ctx = context.WithValue(ctx, chatGroup("openid"), "群主")

    fmt.Println(ctx.Value("openid"))
    fmt.Println(ctx.Value(chatGroup("openid")))
}

输出

不是群主
群主

通过chatGroup,一眼就能看出来是群聊模块的东西

我们再来简写、优化一点

type chatGroup struct{}

func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, "openid", "不是群主")
    ctx = context.WithValue(ctx, chatGroup{}, "群主")

    fmt.Println(ctx.Value("openid"))
    fmt.Println(ctx.Value(chatGroup{}))
}

struct{} 类型(准确来说空结构体是已初始化的值)也可以作为 KV 的 key 类型,当然了,也应该定义为自定义类型。

使用 struct{} 的好处是,这个类型在 Go 中原则上是不占内存空间和 gc 开销的,可以提升性能

进阶例子

封装一个ctx 引入trace ID的案例

// traceid包 用于在 context 中维护 trace ID
package traceid

import "context"

type traceIDKey struct{}

// WithTraceID 往 context 中存入 trace ID
func WithTraceID(ctx context.Context, traceID string) context.Context {
    return context.WithValue(ctx, traceIDKey{}, traceID)
}

// TraceID 从 context 中提取 trace ID
func TraceID(ctx context.Context) string {
    v := context.Value(ctx, traceIDKey{})
    id, _ := v.(string)
    return id
}

到此这篇关于golang中context.WithValue的使用规范问题小结的文章就介绍到这了,更多相关golang context.WithValue内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 三种Golang数组拷贝的实现方式与性能分析

    三种Golang数组拷贝的实现方式与性能分析

    在 Golang 中,有多种方式可以进行数组的拷贝,本文将对其中的三种方式进行性能分析,并比较它们的优缺点,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-06-06
  • Golang如何编写内存高效及CPU调优的Go结构体

    Golang如何编写内存高效及CPU调优的Go结构体

    这篇文章主要介绍了Golang如何编写内存高效及CPU调优的Go结构体,结构体是包含多个字段的集合类型,用于将数据组合为记录
    2022-07-07
  • golang判断文本文件是否是BOM格式的方法详解

    golang判断文本文件是否是BOM格式的方法详解

    在Go语言中,我们可以通过读取文本文件的前几个字节来识别它是否是BOM格式的文件,BOM(Byte Order Mark)是UTF编码标准中的一部分,用于标示文本文件的编码顺序,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • Go语言自带测试库testing使用教程

    Go语言自带测试库testing使用教程

    这篇文章主要为大家介绍了Go语言自带测试库testing使用教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • 详解golang中的闭包与defer

    详解golang中的闭包与defer

    闭包一个函数与其相关的引用环境组合的一个实体,其实可以理解为面向对象中类中的属性与方法,这篇文章主要介绍了golang的闭包与defer,需要的朋友可以参考下
    2022-09-09
  • 为什么不建议在go项目中使用init()

    为什么不建议在go项目中使用init()

    这篇文章主要介绍了为什么不建议在go项目中使用init(),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • go依赖注入库samber/do使用示例讲解

    go依赖注入库samber/do使用示例讲解

    这篇文章主要介绍了go依赖注入库samber/do使用,在本文中,我们学习了如何使用samber/do在 Go 中提供依赖注入,需要的朋友可以参考下
    2024-02-02
  • 深入了解Golang为什么需要超时控制

    深入了解Golang为什么需要超时控制

    本文将介绍为什么需要超时控制,然后详细介绍Go语言中实现超时控制的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-05-05
  • Go slog 日志打印最佳实践案例

    Go slog 日志打印最佳实践案例

    Go 1.21 引入了log/slog 包,为Go语言带来了原生的结构化日志解决方案,本文将深入探讨如何使用slog实现高效、可靠的日志记录,感兴趣的朋友跟随小编一起看看吧
    2025-10-10
  • Golang的Fork/Join实现代码

    Golang的Fork/Join实现代码

    Fork/Join本质上是一种任务分解,将一个很大的任务分解成若干个小任务,然后再对小任务进一步分解,直到最小颗粒度,然后并发执行,对Golang的Fork/Join实现代码感兴趣的朋友跟随小编一起看看吧
    2023-01-01

最新评论