Контексте 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()
передача результатов каждый раз. Чтобы понять, почему это работает таким образом, стоит немного подумать о теории контекста.
контекст на самом деле является узлом в дереве контекстов (следовательно, различные конструкторы контекста принимают "Родительский" контекст). Когда вы запрашиваете значение из контекста, вы фактически запрашиваете первое найденное значение, которое соответствует вашему ключу при поиске по дереву, начиная с контекста в вопрос. Это означает, что если дерево имеет несколько ветвей или вы начинаете с более высокой точки ветви, вы можете найти другое значение. Это часть силы контекстов. Отмена сигналов, с другой стороны, распространение вниз по дереву, чтобы все дочерние элементы один отменили, так что вы можете отменить сигнал ветку, или отменить все дерево.
например, вот дерево контекста, которое содержит различные вещи, которые вы можете хранить в контексты:
черные края представляют поиск данных,а серые края представляют сигналы отмены. Обратите внимание, что они выступают в противоположных направлениях.
Если бы вы использовали карту или другую структуру для хранения ключей, это скорее нарушило бы точку контекстов. Вы больше не сможете отменить только часть запроса или, например. изменение места входа в систему в зависимости от того, в какой части запроса Вы были, так далее.
TL; DR-да, позвоните WithValue несколько раз.