Golang contexto.WithValue: cómo agregar varios pares clave-valor

Con Go context paquete es posible pasar de solicitud de datos específicos para la pila de solicitud de las funciones de manejo de uso de

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

Esto crea un nuevo Context que es una copia de padres y contiene el valor de val que se puede acceder con la clave.

¿Cómo debo proceder si quiero almacenar varios pares clave-valor en un Context? Voy a llamar a WithValue() varias veces, cada vez que pasa el Context recibido de mi última llamada a WithValue()? Esto parece engorroso.

O debo usar una estructura y poner todos mis datos, s.t. Necesito aprobar sólo un valor (que es la estructura), de la cual todas las demás se puede acceder?

O hay una manera de pasar varios pares clave-valor a WithValue()?

InformationsquelleAutor alex | 2016-11-02

4 Kommentare

  1. 30

    Bastante listados sus opciones. La respuesta que usted está buscando depende de cómo desea utilizar los valores almacenados en el contexto.

    contexto.Contexto es un objeto inmutable, «extender» con un par clave-valor sólo es posible haciendo una copia de ella y la adición de la nueva clave-valor para la copia (que se realiza bajo el capó, por el contexto paquete).

    ¿Quieres más controladores para poder acceder a todos los valores de clave de manera transparente? A continuación, agregue todos en un bucle, utilizando siempre el contexto de la última operación.

    Una cosa a tener en cuenta aquí es que el context.Context no utiliza un map bajo el capó para almacenar los pares clave-valor, que puede parecer sorprendente a primera, pero no si usted piensa acerca de ello debe ser inmutable y seguro para el uso concurrente.

    El uso de un map

    Así, por ejemplo, si usted tiene un montón de pares clave-valor y la necesidad de búsqueda de los valores claves de rápido, la adición de cada uno por separado, el resultado será una Context cuya Value() método será lenta. En este caso, es mejor si usted agregar todos los pares clave-valor como una sola map valor, el cual puede ser accedido a través de Context.Value(), y cada uno de los valores que pueden ser consultados por los asociados clave en O(1) tiempo. Sé que esto no va a ser seguro para el uso concurrente a pesar de que, como un mapa puede ser modificado a partir de concurrentes goroutines.

    El uso de un struct

    Si quieres utilizar un gran struct valor de tener campos para todos los pares clave-valor que desea agregar, que también puede ser una opción viable. El acceso a esta estructura con Context.Value() volvería a usted una copia de la estructura, así que sería seguro para el uso concurrente (cada una goroutine sólo se podía conseguir una copia diferente), pero si usted tiene muchos pares de clave-valor, el resultado sería innecesario copia de una gran estructura que cada vez que alguien necesita un solo campo de ella.

    El uso de un híbrido solución

    Un híbrido solución podría ser poner todos los pares clave-valor en un map, y crear un contenedor de estructura para este mapa, ocultando la map (dejados de exportar campo), y sólo proporcionan un getter para los valores almacenados en el mapa. La adición de este contenedor para el contexto, mantener el seguro acceso simultáneo para múltiples goroutines (map es dejados de exportar), sin embargo, no big data necesita ser copiado (map valores son pequeños descriptores sin la llave-valor de datos), y aún así será rápido (como en última instancia, usted índice de un mapa).

    Esta es la forma en que podría parecerse a:

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

    Utilizando:

    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"))

    De salida (intente en la Vaya Zona De Juegos Infantil):

    two

    Si el rendimiento no es crítica (o tiene relativamente pocos pares clave-valor), me gustaría ir con la adición de cada uno por separado.

    • ¿Qué acerca del uso de sync.Map? Supongo que sería lo mejor de lo mejor.
    • Sí, también se podría utilizar sync.Map si necesita mutar los valores en el mapa.
  2. 14

    Sí, usted está en lo correcto, tendrás que llamar a WithValue() pasando en los resultados cada vez. Para entender por qué funciona de esta manera, vale la pena pensar un poco acerca de la teoría detrás de contexto.

    Un contexto es en realidad un nodo en un árbol de contextos (de ahí las diversas contexto constructores de tomar un «padre» contexto). Cuando usted solicita un valor de un contexto, en realidad se está solicitando el primer valor encontrado que coincida con su clave cuando se busca en el árbol, a partir del contexto en cuestión. Esto significa que si el árbol tiene varias ramas, o empezar desde un punto más alto de una rama, se puede encontrar un valor diferente. Esto es parte de la alimentación de los contextos. La cancelación de las señales, por otro lado, propagarse hacia abajo por el árbol a todos los elementos secundarios de la que se canceló, así que usted puede cancelar un singal de la rama, o cancelar la totalidad del árbol.

    Por ejemplo, aquí es un contexto de árbol que contiene varias cosas que usted podría almacenar en contextos:

    Golang contexto.WithValue: cómo agregar varios pares clave-valor

    Los bordes negros representan los datos de las búsquedas, y el gris de las aristas representan la cancelación de las señales. Se nota que le propagarse en direcciones opuestas.

    Si usted fuera a utilizar un mapa o alguna otra estructura para almacenar las llaves, sería más bien romper el punto de contextos. Usted no sería capaz de cancelar sólo una parte de una solicitud, o por ejemplo. cambio donde las cosas se registran dependiendo de qué parte de la solicitud que eran, etc.

    TL;DR — Sí, llame WithValue varias veces.

    • «Si usted fuera a utilizar un mapa o alguna otra estructura para almacenar las llaves, sería más bien romper el punto de contextos.» – si bien esto es cierto, el autor de la pregunta no tiene intención, por ejemplo, cancelar el subárbol a partir de los 3 últimos de los pares que desea agregar. El autor de la pregunta quiere agregar todos sus pares clave-valor, y él ni siquiera lo expone el «camino del medio» contextos (y no son accesibles a través de la Context de la interfaz). Así que en mi opinión en este caso viene con ninguna desventaja la adición de todos los pares clave-valor a un único nodo.
    • Yah, que es justo, es realmente depende de la situación. En general yo diría que «si están separados, sin relación de los valores no tiene sentido para el grupo de ellos», sin embargo. Por ejemplo. si no almacenar juntos en una estructura o mapa normalmente, no lo hagas en un contexto. Este es, por supuesto, es sólo mi preferencia personal y lo que yo considero las mejores prácticas.
    • para el conocimiento más profundo!
  3. 1

    Como «icza», dijo, puede agrupar los valores en una estructura:

    type vars struct {
        lock    sync.Mutex
        db      *sql.DB
    }

    A continuación, puede agregar esta estructura en contexto:

    ctx := context.WithValue(context.Background(), "values", vars{lock: mylock, db: mydb})

    Y se puede recuperar:

    ctxVars, ok := r.Context().Value("values").(vars)
    if !ok {
        log.Println(err)
        return err
    }
    db := ctxVars.db
    lock := ctxVars.lock
  4. 0

    Uno (funcional) de una forma de hacerlo es utilizando alarmada y cierres de

    r.WithContext(BuildContext(
       r.Context(),
       SchemaId(mySchemaId),
       RequestId(myRequestId),
       Logger(myLogger)
    ))
    
    func RequestId(id string) partialContextFn {
       return func(ctx context.Context) context.Context {
          return context.WithValue(ctx, requestIdCtxKey, requestId)
       }
    }
    
    func BuildContext(ctx context.Context, ctxFns ...partialContextFn) context.Context {
       for f := range ctxFns {
          ctx = f(ctx)
       }
    
       return ctx
    } 
    
    type partialContextFn func(context.Context) context.Context

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea