I programmi che eseguono più calcoli simultanei nelle goroutine devono gestirne la durata. Le goroutines in fuga possono entrare in loop infiniti, mettere in stallo altre goroutine in attesa o semplicemente impiegare troppo tempo. Idealmente, dovresti essere in grado di cancellare le goroutine o farle scadere dopo una moda.
Inserisci la programmazione basata sul contenuto. Go 1.7 ha introdotto il pacchetto di contesto, che fornisce esattamente quelle funzionalità e la possibilità di associare valori arbitrari a un contesto che viaggia con l'esecuzione di richieste e consente la comunicazione fuori banda e il passaggio di informazioni.
In questo tutorial, imparerai i dettagli dei contesti in Go, quando e come usarli, e come evitare di abusarli.
Il contesto è un'astrazione molto utile. Ti consente di incapsulare informazioni che non sono rilevanti per il calcolo di base come ID richiesta, token di autorizzazione e timeout. Ci sono molti vantaggi di questo incapsulamento:
Ecco l'intera interfaccia di contesto:
digitare Context interface Deadline () (deadline time.Time, ok bool) Fine () <-chan struct Err() error Value(key interface) interface
Le seguenti sezioni spiegano lo scopo di ciascun metodo.
La scadenza restituisce il momento in cui il lavoro svolto a nome di questo contesto deve essere annullato. Termine di reso ok == false
quando non è impostata alcuna scadenza. Le chiamate successive alla scadenza restituiscono gli stessi risultati.
Fine () restituisce un canale che viene chiuso quando il lavoro svolto a nome di questo contesto deve essere annullato. Fatto può restituire nil se questo contesto non può mai essere annullato. Le chiamate successive a Done () restituiscono lo stesso valore.
Fatto può essere utilizzato in istruzioni selezionate:
// Stream genera valori con DoSomething e li invia // a out finché DoSomething non restituisce un errore o ctx.Done è // closed. func Stream (ctx context.Context, out chan<- Value) error for v, err := DoSomething(ctx) if err != nil return err select case <-ctx.Done(): return ctx.Err() case out <- v:
Vedi questo articolo dal blog Go per ulteriori esempi su come utilizzare un canale Done per la cancellazione.
Err () restituisce nil finché il canale Done è aperto. Ritorna Annullato
se il contesto è stato cancellato o DeadlineExceeded
se la scadenza del contesto è scaduta o il timeout è scaduto. Dopo aver terminato Done, le successive chiamate a Err () restituiscono lo stesso valore. Ecco le definizioni:
// Cancellato è l'errore restituito da Context.Err quando il // contesto viene cancellato. var Cancelled = errors.New ("context deleted") // DeadlineExceeded è l'errore restituito da Context.Err // quando la scadenza del contesto passa. var DeadlineExceeded error = deadlineExceededError
Value restituisce il valore associato a questo contesto per una chiave, o zero se nessun valore è associato alla chiave. Le chiamate successive a Valore con la stessa chiave restituiscono lo stesso risultato.
Utilizzare i valori di contesto solo per i dati con ambito di richiesta che gestiscono i processi ei limiti dell'API, non per passare i parametri facoltativi alle funzioni.
Una chiave identifica un valore specifico in un contesto. Le funzioni che desiderano memorizzare i valori nel contesto in genere allocano una chiave in una variabile globale e la utilizzano come argomento di context.WithValue () e Context.Value (). Una chiave può essere di qualsiasi tipo che supporti l'uguaglianza.
I contesti hanno ambiti. È possibile derivare ambiti da altri ambiti e l'ambito principale non ha accesso ai valori negli ambiti derivati, ma gli ambiti derivati hanno accesso ai valori degli ambiti del genitore.
I contesti formano una gerarchia. Si inizia con context.Background () o context.TODO (). Ogni volta che chiami WithCancel (), WithDeadline () o WithTimeout (), crei un contesto derivato e ricevi una funzione cancel. L'importante è che quando un contesto genitore viene cancellato o scaduto, tutti i suoi contesti derivati.
Dovresti usare context.Background () nelle funzioni main (), init () e tests. Dovresti usare context.TODO () se non sei sicuro di quale contesto usare.
Si noti che lo sfondo e TODO sono non cancellabile.
Come ricordi, WithDeadline () e WithTimeout () restituiscono i contesti che vengono cancellati automaticamente, mentre WithCancel () restituisce un contesto e deve essere cancellato esplicitamente. Tutti loro restituiscono una funzione di annullamento, quindi anche se il timeout / scadenza non ha ancora scadenza, è comunque possibile annullare qualsiasi contesto derivato.
Esaminiamo un esempio. Innanzitutto, ecco la funzione contextDemo () con un nome e un contesto. Funziona in un ciclo infinito, stampando sulla console il suo nome e la scadenza del suo contesto, se presente. Quindi dorme solo per un secondo.
pacchetto main import ("fmt" "context" "time") func contextDemo (nome stringa, ctx context.Context) per if ok fmt.Println (name, "scadrà a:", scadenza) else fmt .Println (nome, "non ha scadenza") time.Sleep (time.Second)
La funzione principale crea tre contesti:
Quindi, avvia la funzione contextDemo come tre goroutine. Tutti corrono simultaneamente e stampano il loro messaggio ogni secondo.
La funzione principale attende quindi che la goroutine con il timeoutCancel venga cancellata leggendo dal suo canale Done () (bloccherà finché non sarà chiuso). Una volta scaduto il timeout dopo tre secondi, main () chiama cancelFunc () che annulla la goroutine con cancelContext e l'ultima goroutine con il contesto di scadenza quattro ore derivato.
func main () timeout: = 3 * time.Second deadline: = time.Now (). Add (4 * time.Hour) timeOutContext, _: = context.WithTimeout (context.Background (), timeout) cancelContext, cancelFunc : = context.WithCancel (context.Background ()) deadlineContext, _: = context.WithDeadline (cancelContext, deadline) go contextDemo ("[timeoutContext]", timeOutContext) go contextDemo ("[cancelContext]", cancelContext) go contextDemo ( "[deadlineContext]", deadlineContext) // Attendi che scada il timeout <- timeOutContext.Done() // This will cancel the deadline context as well as its // child - the cancelContext fmt.Println("Cancelling the cancel context… ") cancelFunc() <- cancelContext.Done() fmt.Println("The cancel context has been cancelled… ") // Wait for both contexts to be cancelled <- deadlineContext.Done() fmt.Println("The deadline context has been cancelled… ")
Ecco l'output:
[cancelContext] non ha scadenza [deadlineContext] scadrà alle: 2017-07-29 09: 06: 02.34260363 [timeoutContext] scadrà alle: 2017-07-29 05: 06: 05.342603759 [cancelContext] non ha scadenza [timeoutContext] scadenza: 2017-07-29 05: 06: 05.342603759 [deadlineContext] scadrà alle: 2017-07-29 09: 06: 02.34260363 [cancelContext] non ha scadenza [timeoutContext] scadrà tra: 2017-07-29 05: 06: 05.342603759 [deadlineContext] scadrà alle: 2017-07-29 09: 06: 02.34260363 Annullamento del contesto di cancellazione ... Il contesto di cancellazione è stato cancellato ... Il contesto di scadenza è stato cancellato ...
È possibile allegare valori a un contesto utilizzando la funzione WithValue (). Si noti che viene restituito il contesto originale, non un contesto derivato. Puoi leggere i valori dal contesto usando il metodo Value (). Modifichiamo la nostra funzione demo per ottenere il suo nome dal contesto invece di passarlo come parametro:
func contextDemo (ctx context.Context) deadline, ok: = ctx.Deadline () nome: = ctx.Value ("nome") per if ok fmt.Println (name, "scadrà a:", scadenza) else fmt.Println (nome, "non ha scadenza") time.Sleep (time.Second)
E modifichiamo la funzione principale per allegare il nome tramite WithValue ():
go contextDemo (context.WithValue (timeOutContext, "name", "[timeoutContext]")) go contextDemo (context.WithValue (cancelContext, "name", "[cancelContext]")) go contextDemo (context.WithValue (deadlineContext, " nome "," [deadlineContext] "))
L'uscita rimane la stessa. Consulta la sezione sulle best practice per alcune linee guida sull'utilizzo appropriato dei valori di contesto.
Diverse migliori pratiche sono emerse intorno ai valori del contesto:
Uno dei casi d'uso più utili per i contesti è il passaggio di informazioni insieme a una richiesta HTTP. Queste informazioni possono includere un ID di richiesta, credenziali di autenticazione e altro. In Go 1.7, il pacchetto standard net / http ha sfruttato il pacchetto di contesto ottenendo "standardizzato" e aggiunto il supporto contestuale direttamente all'oggetto richiesta:
func (r * Request) Context () context.Context func (r * Request) WithContext (ctx context.Context) * Richiesta
Ora è possibile allegare un ID di richiesta dalle intestazioni fino al gestore finale in un modo standard. La funzione di gestore WithRequestID () estrae un ID di richiesta dall'intestazione "X-Request-ID" e genera un nuovo contesto con l'ID di richiesta da un contesto esistente che utilizza. Quindi lo passa al successivo gestore della catena. La funzione pubblica GetRequestID () fornisce l'accesso ai gestori che possono essere definiti in altri pacchetti.
const requestIDKey int = 0 func WithRequestID (next http.Handler) http.Handler return http.HandlerFunc (func (rw http.ResponseWriter, req * http.Request) // Estrai ID richiesta dall'intestazione della richiesta reqID: = req.Header .Get ("X-Request-ID") // Crea un nuovo contesto dal contesto della richiesta con // l'ID della richiesta ctx: = context.WithValue (req.Context (), requestIDKey, reqID) // Crea nuova richiesta con il nuovo context req = req.WithContext (ctx) // Lascia che il prossimo gestore della catena prenda il sopravvento next.ServeHTTP (rw, req)) func GetRequestID (ctx context.Context) string ctx.Value (requestIDKey). ( string) func Handle (rw http.ResponseWriter, req * http.Request) reqID: = GetRequestID (req.Context ()) ... func main () handler: = WithRequestID (http.HandlerFunc (Handle)) http. ListenAndServe ("/", gestore)
La programmazione basata sul contesto fornisce un modo standard e ben supportato per risolvere due problemi comuni: gestire la durata delle goroutine e passare informazioni fuori banda attraverso una catena di funzioni.
Segui le migliori pratiche e usa i contesti nel giusto contesto (vedi cosa ho fatto lì?) E il tuo codice migliorerà considerevolmente.