使用 Go 的 context
包可以将特定于请求的数据传递到请求处理函数的堆栈,使用
func WithValue(parent Context, key, val interface{}) Context
这将创建一个新的 Context
它是 parent 的副本并包含可以使用键访问的值 val。
如果我想在 Context
中存储多个键值对,我该如何继续?我应该打电话给 WithValue()
几次,每次都传递我上次打电话给 Context
收到的 WithValue()
吗?这显得很麻烦。
或者我应该使用一个结构并将所有数据放在那里,我只需要传递一个值(即结构),从中可以访问所有其他值吗?
或者有没有办法将几个键值对传递给 WithValue()
?
原文由 alex 发布,翻译遵循 CC BY-SA 4.0 许可协议
你几乎列出了你的选择。您正在寻找的答案取决于您希望如何使用存储在上下文中的值。
context.Context
是一个不可变对象,只有通过复制它并将新的键值添加到副本中才能使用键值对“扩展”它(这是在幕后完成的,通过context
包)。您是否希望进一步的处理程序能够以透明的方式按键访问所有值?然后在循环中添加所有内容,始终使用最后一个操作的上下文。
这里要注意的一件事是
context.Context
不使用map
在引擎盖下存储键值对,一开始听起来可能令人惊讶,但如果你考虑它必须是不可变的并且可以安全地同时使用。使用
map
因此,例如,如果您有很多键值对并且需要通过键 快速 查找值,则分别添加每个键值对将导致
Context
其Value()
方法会很慢。在这种情况下,最好将所有键值对添加为单个map
值,可以通过Context.Value()
访问,并且其中的每个值都可以通过关联查询键入O(1)
时间。知道这对于并发使用来说是不安全的,因为映射可能会从并发 goroutines 中修改。使用
struct
如果您使用一个大的
struct
值,其中包含要添加的所有键值对的字段,这也可能是一个可行的选择。使用Context.Value()
访问这个结构将返回给你一个结构的副本,所以并发使用是安全的(每个 goroutine 只能得到一个不同的副本),但是如果你有很多键值对,每当有人需要其中的一个字段时,这将导致不必要地复制一个大结构。使用 混合 解决方案
混合 解决方案可能是将所有键值对放在
map
中,并为此映射创建一个包装器结构,隐藏map
(未导出的字段),并仅提供一个存储在地图中的值的吸气剂。仅将此包装器添加到上下文中,您可以保持多个 goroutine 的 _安全并发访问_(map
未导出),但 _不需要复制大数据_(map
值是小描述符,没有键值数据),而且它仍然会 _很快_(因为最终你会索引地图)。这就是它的样子:
使用它:
输出(在 Go Playground 上尝试):
如果性能不是很重要(或者您的键值对相对较少),我会单独添加每个键值对。