context.WithValue:如何添加多个键值对

新手上路,请多包涵

使用 Go 的 context 包可以将特定于请求的数据传递到请求处理函数的堆栈,使用

func WithValue(parent Context, key, val interface{}) Context

这将创建一个新的 Context 它是 parent 的副本并包含可以使用键访问的值 val。

如果我想在 Context 中存储多个键值对,我该如何继续?我应该打电话给 WithValue() 几次,每次都传递我上次打电话给 Context 收到的 WithValue() 吗?这显得很麻烦。

或者我应该使用一个结构并将所有数据放在那里,我只需要传递一个值(即结构),从中可以访问所有其他值吗?

或者有没有办法将几个键值对传递给 WithValue()

原文由 alex 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 3.4k
2 个回答

你几乎列出了你的选择。您正在寻找的答案取决于您希望如何使用存储在上下文中的值。

context.Context 是一个不可变对象,只有通过复制它并将新的键值添加到副本中才能使用键值对“扩展”它(这是在幕后完成的,通过 context 包)。

您是否希望进一步的处理程序能够以透明的方式按键访问所有值?然后在循环中添加所有内容,始终使用最后一个操作的上下文。

这里要注意的一件事是 context.Context 不使用 map 在引擎盖下存储键值对,一开始听起来可能令人惊讶,但如果你考虑它必须是不可变的并且可以安全地同时使用。

使用 map

因此,例如,如果您有很多键值对并且需要通过键 快速 查找值,则分别添加每个键值对将导致 ContextValue() 方法会很慢。在这种情况下,最好将所有键值对添加为单个 map 值,可以通过 Context.Value() 访问,并且其中的每个值都可以通过关联查询键入 O(1) 时间。知道这对于并发使用来说是不安全的,因为映射可能会从并发 goroutines 中修改。

使用 struct

如果您使用一个大的 struct 值,其中包含要添加的所有键值对的字段,这也可能是一个可行的选择。使用 Context.Value() 访问这个结构将返回给你一个结构的副本,所以并发使用是安全的(每个 goroutine 只能得到一个不同的副本),但是如果你有很多键值对,每当有人需要其中的一个字段时,这将导致不必要地复制一个大结构。

使用 混合 解决方案

混合 解决方案可能是将所有键值对放在 map 中,并为此映射创建一个包装器结构,隐藏 map (未导出的字段),并仅提供一个存储在地图中的值的吸气剂。仅将此包装器添加到上下文中,您可以保持多个 goroutine 的 _安全并发访问_( map 未导出),但 _不需要复制大数据_( map 值是小描述符,没有键值数据),而且它仍然会 _很快_(因为最终你会索引地图)。

这就是它的样子:

 type Values struct {
    m map[string]string
}

func (v Values) Get(key string) string {
    return v.m[key]
}

使用它:

 v := Values{map[string]string{
    "1": "one",
    "2": "two",
}}

c := context.Background()
c2 := context.WithValue(c, "myvalues", v)

fmt.Println(c2.Value("myvalues").(Values).Get("2"))

输出(在 Go Playground 上尝试):

 two

如果性能不是很重要(或者您的键值对相对较少),我会单独添加每个键值对。

原文由 icza 发布,翻译遵循 CC BY-SA 3.0 许可协议

是的,你是对的,你需要调用 WithValue() 每次传递结果。要理解它为何以这种方式工作,值得思考一下上下文背后的理论。

上下文实际上是上下文树中的一个节点(因此各种上下文构造函数采用“父”上下文)。当您从上下文中请求一个值时,您实际上是在从相关上下文开始搜索树时请求找到的与您的键匹配的第一个值。这意味着如果您的树有多个分支,或者您从分支中较高的点开始,您可能会找到不同的值。这是语境的一部分力量。另一方面,取消信号沿着树向下传播到被取消的元素的所有子元素,因此您可以取消单个分支或取消整个树。

例如,这里有一个上下文树,其中包含您可能存储在上下文中的各种内容:

上下文的树表示

黑色边缘代表数据查找,灰色边缘代表取消信号。请注意,它们以相反的方向传播。

如果您要使用映射或其他一些结构来存储您的密钥,那么它宁愿打破上下文的要点。您将不再能够仅取消请求的一部分,或者例如。根据您所在的请求部分等更改记录内容的位置。

TL;DR — 是的,多次调用 WithValue。

原文由 Sam Whited 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题