Контексте Golang.WithValue: как добавить несколько пар ключ-значение

С гоу context пакет можно передать данные запроса в стек функций обработки запросов с помощью

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

это создает новый Context который является копией родителя и содержит значение val, к которому можно получить доступ с помощью ключа.

как мне действовать, если я хочу сохранить несколько пар ключ-значение в Context? Позвать WithValue() несколько раз, каждый раз проходя мимо Context получен от моего последнего звонка в WithValue()? Это появляется обременительный.
Или я должен использовать структуру и поместить все свои данные туда, s.т. Мне нужно передать только одно значение (которое является структурой), из которого можно получить доступ ко всем остальным?

или есть способ передать несколько пар ключ-значение в WithValue()?

2 ответов


вы в значительной степени перечислили свои варианты. Ответ, который вы ищете зависит от того, как вы хотите использовать значения, хранящиеся в контексте.

context.Context является неизменяемым объектом, "расширение" его с помощью пары ключ-значение возможно только путем создания его копии и добавления нового значения ключа к копии (что делается под капотом,context пакет).

вы хотите, чтобы дальнейшие обработчики могли получить доступ ко всем значениям по ключу прозрачным способом? Затем добавьте все в цикл, используя всегда контекст последней операции.

одна вещь, чтобы отметить здесь, что context.Context не использовать map под капотом для хранения пар ключ-значение, что может показаться удивительным сначала, но не если вы думаете, что это должно быть неизменным и безопасным для одновременного использования.

С помощью map

например, если у вас много пар ключ-значение и вам нужно искать значения по ключи!--37-->быстро, добавление каждого отдельно приведет к Context чей Value() метод будет медленным. В этом случае лучше добавить все пары ключ-значение как одно map значение, к которому можно получить доступ через Context.Value(), и каждое значение в нем может быть запрошено связанным ключом в O(1) времени. Знайте, что это не будет безопасно для одновременного использования, поскольку карта может быть изменена из параллельных goroutines.

С помощью struct

если бы вы использовали большой struct значение поля для всех пар ключ-значение, которые вы хотите добавить, что также может быть жизнеспособным вариантом. Доступ к этой структуре с помощью Context.Value() вернет вам копию структуры, поэтому она будет безопасна для одновременного использования (каждая goroutine может получить только другую копию), но если у вас есть много пар ключ-значение, это приведет к ненужной копии большой структуры каждый раз, когда кому-то нужно одно поле из он.

С помощью гибрид решение

A гибрид решение может заключаться в том, чтобы поместить все ваши пары ключ-значение в map, и создайте структуру оболочки для этой карты, скрывая map (поле неэкспортируется), и предоставлять только геттер для значений, хранящихся в карте. Добавляя только эту оболочку в контекст, вы сохраняете безопасный одновременный доступ для нескольких goroutines (map не сообщается), пока нет большие данные должны быть скопированы (map значения-это небольшие дескрипторы без данных key-value), и все равно это будет быстро (как в конечном счете вы будете индексировать карту).

вот как это может выглядеть:

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

если производительность не критична (или у вас относительно мало пар ключ-значение), я бы пошел с добавлением каждого отдельно.


Да, вы правы, вам нужно позвонить WithValue() передача результатов каждый раз. Чтобы понять, почему это работает таким образом, стоит немного подумать о теории контекста.

контекст на самом деле является узлом в дереве контекстов (следовательно, различные конструкторы контекста принимают "Родительский" контекст). Когда вы запрашиваете значение из контекста, вы фактически запрашиваете первое найденное значение, которое соответствует вашему ключу при поиске по дереву, начиная с контекста в вопрос. Это означает, что если дерево имеет несколько ветвей или вы начинаете с более высокой точки ветви, вы можете найти другое значение. Это часть силы контекстов. Отмена сигналов, с другой стороны, распространение вниз по дереву, чтобы все дочерние элементы один отменили, так что вы можете отменить сигнал ветку, или отменить все дерево.

например, вот дерево контекста, которое содержит различные вещи, которые вы можете хранить в контексты:

Tree representation of context

черные края представляют поиск данных,а серые края представляют сигналы отмены. Обратите внимание, что они выступают в противоположных направлениях.

Если бы вы использовали карту или другую структуру для хранения ключей, это скорее нарушило бы точку контекстов. Вы больше не сможете отменить только часть запроса или, например. изменение места входа в систему в зависимости от того, в какой части запроса Вы были, так далее.

TL; DR-да, позвоните WithValue несколько раз.